<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Misleading Metaphors]]></title><description><![CDATA[Personal blog of Saïd (ayewo).]]></description><link>https://ayewo.com/</link><image><url>https://ayewo.com/favicon.png</url><title>Misleading Metaphors</title><link>https://ayewo.com/</link></image><generator>Ghost 5.53</generator><lastBuildDate>Wed, 21 Feb 2024 19:20:21 GMT</lastBuildDate><atom:link href="https://ayewo.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Programmatic Creation of the Ghost Admin User]]></title><description><![CDATA[<h2 id="problem">Problem</h2><p>You want to completely automate the installation of Ghost including creation of the admin user?</p><p>You search on Google and land on this exactly worded <a href="https://forum.ghost.org/t/creating-admin-user-programmatically/8952?ref=ayewo.com">forum post</a> from 2019 with no ready-to-use answer.</p><h2 id="solution">Solution</h2><!--kg-card-begin: markdown--><h3 id="you-can-do-it-using-curl">You can do it using <code>curl</code>:</h3>
<pre><code class="language-bash">curl &apos;http://localhost:2368/ghost/api/admin/authentication/</code></pre>]]></description><link>https://ayewo.com/programmatic-creation-of-the-ghost-admin-user/</link><guid isPermaLink="false">64a33e7c3b652d31711a981a</guid><category><![CDATA[ghost.sh]]></category><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Mon, 03 Jul 2023 21:35:05 GMT</pubDate><content:encoded><![CDATA[<h2 id="problem">Problem</h2><p>You want to completely automate the installation of Ghost including creation of the admin user?</p><p>You search on Google and land on this exactly worded <a href="https://forum.ghost.org/t/creating-admin-user-programmatically/8952?ref=ayewo.com">forum post</a> from 2019 with no ready-to-use answer.</p><h2 id="solution">Solution</h2><!--kg-card-begin: markdown--><h3 id="you-can-do-it-using-curl">You can do it using <code>curl</code>:</h3>
<pre><code class="language-bash">curl &apos;http://localhost:2368/ghost/api/admin/authentication/setup/&apos; \
-X &apos;POST&apos; \
-H &apos;Content-Type: application/json; charset=UTF-8&apos; \
-H &apos;Accept: application/json, text/javascript, */*; q=0.01&apos; \
-H &apos;Host: localhost:2368&apos; \
-H &apos;Origin: http://localhost:2368&apos; \
-H &apos;Referer: http://localhost:2368/ghost/&apos; \
-H &apos;Connection: keep-alive&apos; \
-H &apos;X-Requested-With: XMLHttpRequest&apos; \
-H &apos;App-Pragma: no-cache&apos; \
--data-binary &apos;{&quot;setup&quot;:[{&quot;name&quot;:&quot;Admin&quot;,&quot;email&quot;:&quot;email@ghost.example.com&quot;,&quot;password&quot;:&quot;&lt;ghost-admin-password&gt;&quot;,&quot;blogTitle&quot;:&quot;Blog Title&quot;}]}&apos;
</code></pre>
<p>You only need to change the following values in the payload:</p>
<ul>
<li><code>email</code>;</li>
<li><code>password</code>,</li>
<li><code>blogTitle</code>.</li>
</ul>
<!--kg-card-end: markdown--><p>This solution was used in a <a href="https://ayewo.com/how-to-host-a-new-ghost-blog-on-aws/">[:larger article]</a> so you may want to read it as well.</p><!--kg-card-begin: markdown--><h2 id="postscript">PostScript</h2>
<p>The Ghost CTO did offer the following suggestion, 2 years later:</p>
<blockquote>
<p>You can automate booting Ghost to initialise the DB and then calling the setup API endpoint to finalise setup so that <strong>the window of opportunity is a few ms</strong>.</p>
</blockquote>
<p>I did all of my research on a server with a public IP, and since it would take me more than a few ms before I could figure out how to correctly create the admin user programmatically, I had to take some precautions.</p>
<p>Rather than expose the unfinished Ghost Admin setup page over the public Internet, I decided to leave it accessible only from <code>localhost</code> and used SSH tunneling to access it from my machine.</p>
<p>To figure out the right incantations to correctly create an admin user from the CLI using <code>curl</code>, I installed a development instance of Ghost:</p>
<pre><code class="language-bash">su - ghost-mgr
sudo mkdir -p /var/www/local &amp;&amp; cd /var/www/local
sudo chown -R ghost-mgr:ghost-mgr /var/www/local
ghost install local
</code></pre>
<p>This will create an entry in <code>~/.ghost/config</code>:</p>
<pre><code class="language-bash">{
  &quot;instances&quot;: {
    &quot;ghost1-ayewo-com&quot;: {
      &quot;cwd&quot;: &quot;/var/www/ghost&quot;
    },
    &quot;ghost-local&quot;: {
      &quot;cwd&quot;: &quot;/var/www/local&quot;
    }
  }
}
</code></pre>
<p>Each time you use the <code>ghost-cli</code> to interact with one or more instances of Ghost on your machine, it will update its entry inside <code>/home/ghost-mgr/.ghost/config</code>:</p>
<pre><code class="language-bash">ghost ls
&#x250C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x252C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x252C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x252C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x252C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x252C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x252C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2510;
&#x2502; Name             &#x2502; Location       &#x2502; Version &#x2502; Status  &#x2502; URL &#x2502; Port &#x2502; Process Manager &#x2502;
&#x251C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x253C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x253C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x253C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x253C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x253C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x253C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2524;
&#x2502; ghost1-ayewo-com &#x2502; /var/www/ghost &#x2502; 5.46.1  &#x2502; stopped &#x2502; n/a &#x2502; n/a  &#x2502; n/a             &#x2502;
&#x251C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x253C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x253C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x253C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x253C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x253C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x253C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2524;
&#x2502; ghost-local      &#x2502; /var/www/local &#x2502; 5.51.2  &#x2502; stopped &#x2502; n/a &#x2502; n/a  &#x2502; n/a             &#x2502;
&#x2514;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2534;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2534;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2534;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2534;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2534;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2534;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2518;
</code></pre>
<p>After figuring out the right <code>curl</code> incantations, I tested it against the <em>development</em> instance of Ghost and quickly encountered a bug where Ghost couldn&apos;t write to the SQLite database due to some <a href="https://stackoverflow.com/a/1518771/?ref=ayewo.com">file permissions issue</a>.</p>
<p>So I tried this quick fix since Ghost in production requires this folder to be owned by a non-privileged user <code>ghost</code> (not <code>ghost-mgr</code>):</p>
<pre><code class="language-bash">sudo chown -R ghost:ghost /var/www/local/content/
</code></pre>
<p>Ultimately I reverted it because the <em>development</em> instance of Ghost complained:</p>
<pre><code class="language-bash">&#x2716; Starting Ghost: ghost-local
A SystemError occurred.

Message: The content folder is not owned by the current user.
Ensure the content folder has correct permissions and try again.
</code></pre>
<p>The error went away after a <code>ghost stop</code>/<code>ghost start</code> cycle (I think?), so a workaround might be to simply restart Ghost immediately after installation of the development instance i.e.:</p>
<pre><code class="language-bash"># after you install a development instance with
ghost install local

# restart it before continuing
ghost restart ghost-local

Your admin interface is located at:

    http://localhost:2368/ghost/
</code></pre>
<blockquote>
<p><strong>Note</strong>: The presence of this development instance will always cause Ghost to assume any attempts to run <code>ghost start</code> inside <code>/var/www/ghost</code> (the production instance path) or even invoking the name of the production instance explicitly (e.g. <code>cd /var/www/ghost &amp;&amp; ghost start ghost1-ayewo-com</code>), is an attempt at running the development instance installed at <code>/var/www/local</code>.</p>
</blockquote>
<blockquote>
<p>Each time I tried to start the production instance while the development instance was still installed, this terrible assumption caused Ghost to output an inscrutable error even when the JSON at <code>/var/www/ghost/config.production.json</code> is perfectly valid:</p>
</blockquote>
<pre><code class="language-bash">1) Validating config

Error detected in the development configuration.

Message: Config file is not valid JSON
</code></pre>
<blockquote>
<p>It always prefaced its outputs with &quot;Running in development mode&quot; even when the config file inside <code>/var/www/ghost</code> is explicitly named <code>config.production.json</code> and not <code>config.development.json</code>.</p>
</blockquote>
<blockquote>
<p>The fix was to relocate the development instance folder <code>/var/www/local</code> to another location where the <code>ghost-cli</code> cannot find it before continuing.</p>
</blockquote>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[How to Host a New Ghost Blog on AWS]]></title><description><![CDATA[This article includes step-by-step information for setting up a brand new Ghost blog on an EC2 instance running Ubuntu.]]></description><link>https://ayewo.com/how-to-host-a-new-ghost-blog-on-aws/</link><guid isPermaLink="false">64a2981b3b652d31711a9611</guid><category><![CDATA[ghost.sh]]></category><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Mon, 03 Jul 2023 17:53:37 GMT</pubDate><content:encoded><![CDATA[<p>The instructions in this article is a chimera between the documentation for installing <a href="https://ghost.org/docs/install/ubuntu/?ref=ayewo.com">Ghost on Ubuntu</a> and the Ghost 1-Click app on the <a href="https://marketplace.digitalocean.com/apps/ghost?ref=ayewo.com" rel="nofollow">DigitalOcean Marketplace</a>.</p><p>These instructions have been made into an open source project called <code><a href="https://github.com/ayewo/ghost.sh?ref=ayewo.com">ghost.sh</a></code>. </p><p>The project will convert all the steps into an easy-to-use 1-Click script that can be used to set up a brand new Ghost blog on any cloud provider in just a few minutes. I also wrote an announcement post that delves into my <a href="https://ayewo.com/ghost-sh/">[:motivation behind]</a> the project.</p><!--kg-card-begin: markdown--><h2 id="1-ec2">1. EC2</h2>
<p>Create a <code>t2.micro</code> instance either using the AWS CLI or the AWS Console.</p>
<ul>
<li>Pick an Ubuntu AMI. I used an Ubuntu 22.04 server for my EC2 instance based on this AMI: <a href="https://eu-west-2.console.aws.amazon.com/ec2/home?region=eu-west-2&amp;ref=ayewo.com#Images:visibility=public-images;imageId=ami-09744628bed84e434"><code>ami-09744628bed84e434</code></a>.</li>
<li>Enable Elastic IP when setting up your instance. This allows you to hold on to the same public IP (between reboots) for as long as you need.</li>
<li>In the settings for your Security Group (SG), be sure to open ports <code>80 (http), 443 (https) and 22 (ssh)</code>.</li>
</ul>
<p>Ghost can automate the request for a <a href="https://letsencrypt.org/?ref=ayewo.com">Let&apos;s Encrypt</a> certificate to secure your HTTP traffic but the domain verification process can only be done over HTTP which is why you must have port 80 open in your SG.</p>
<blockquote>
<p><strong>Note</strong><br>
I originally started with a <code>t2.micro</code> EC2 instance but noticed that <code>ghost install</code> was quite slow. It took up to 40 mins to download ~450MB on the instance so I restarted the process on a <code>t2.small</code> instance. Also, for some reason, once <code>ghost setup</code> is complete, the installation ballooned in size to ~880MB but the issue appears to have went away with the switch to a <code>t2.small</code>.</p>
</blockquote>
<h2 id="2-dns">2. DNS</h2>
<p>2.1. Login to your domain registrar and associate your Elastic IP with your domain. I use namecheap.com so from the &quot;<a href="https://ap.www.namecheap.com/Domains/DomainControlPanel/example.com/advancedns?ref=ayewo.com">Advanced DNS</a>&quot; page for my domain, I added an <code>A Record</code> that mapped the Elastic IP of the <code>t2.small</code> instance to <code>ghost.example.com</code> and used a TTL of 1 minute so the changes can be picked up quickly.</p>
<p>While I wait for the DNS changes to propagate, I&apos;ll append an entry to my local <code>/etc/hosts</code> so that <code>ghost.example.com</code> is resolved immediately by my machine. (Don&apos;t worry, we will check that <code>nslookup ghost.example.com</code> works correctly later.)</p>
<p>2.2. Open Terminal tab, then run: <code>sudo vim /etc/hosts</code> to append a temporary mapping for the Elastic IP to your domain.</p>
<pre><code class="language-bash">18.1.1.1  ghost.example.com 
</code></pre>
<p>2.3. Next, login to the EC2 instance using the domain name (you could also use the Elastic IP directly).</p>
<pre><code class="language-bash"># Unlike DigitalOcean which uses &apos;root@&apos; for remote SSH, AWS AMIs use &apos;ubuntu@&apos; 
ssh -v -i ~/.ssh/ec2-keypair.pem ubuntu@ghost.example.com

Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-1031-aws x86_64)

  ...
  System information as of Mon May  1 17:26:05 UTC 2023

  System load:  0.0               Processes:             95
  Usage of /:   5.3% of 28.89GB   Users logged in:       0
  Memory usage: 21%               IPv4 address for eth0: 172.31.17.58
  Swap usage:   0%

...
To run a command as administrator (user &quot;root&quot;), use &quot;sudo &lt;command&gt;&quot;.
See &quot;man sudo_root&quot; for details.
</code></pre>
<p>2.4. While logged into the EC2 instance, confirm that the DNS changes have now propagated:</p>
<pre><code class="language-bash">nslookup ghost.example.com
Server:   127.0.0.53
Address:  127.0.0.53#53

Non-authoritative answer:
Name: ghost.example.com
Address: 18.1.1.1
</code></pre>
<h2 id="3-ghost">3. Ghost</h2>
<p>These steps are based directly on the instructions at <a href="https://ghost.org/docs/install/ubuntu/?ref=ayewo.com">How to install Ghost on Ubuntu</a>.</p>
<h3 id="31-create-a-sudoer-and-update-the-server">3.1. Create a <a href="https://en.wiktionary.org/wiki/sudoer?ref=ayewo.com"><code>sudoer</code></a> and Update the Server</h3>
<pre><code class="language-bash"># Launch the &apos;screen&apos; command with logging in case you are working from a flaky connection like me.
cd /tmp &amp;&amp; screen -L -S a01 -Logfile a01.log  # -Logfile &lt;log-file&gt; only works in screen v4.06.02+
cd /tmp &amp;&amp; echo &quot;logfile a01.log&quot; &gt;&gt; a01.rc &amp;&amp; screen -c /tmp/a01.rc -L -S a01  # hack for screen v4.03.01 &amp; older 

# Ghost UNIX account setup
# Create a new user and follow prompts
sudo adduser ghost-mgr

# Add user to superuser group to unlock admin privileges
sudo usermod -aG sudo ghost-mgr

# Remove the password you picked in &apos;adduser&apos;
sudo passwd -d ghost-mgr

# Then log in as the new user
su - ghost-mgr

# You can also use
# sudo -i -u ghost-mgr

# Update the server
# Update package lists
sudo apt-get update

# Update installed packages
sudo apt-get upgrade
</code></pre>
<details>
<summary><strong>Note</strong> regarding <code>sudo apt-get upgrade</code></summary>
Towards the end, the command produced the following output:
<pre>
Restarting services...
 systemctl restart multipathd.service packagekit.service polkit.service rsyslog.service ssh.service
Service restarts being deferred:
 /etc/needrestart/restart.d/dbus.service
 systemctl restart networkd-dispatcher.service
 systemctl restart systemd-logind.service
 systemctl restart unattended-upgrades.service
 systemctl restart user@1000.service
<p>No containers need to be restarted.</p>
<p>No user sessions are running outdated binaries.</p>
<p>No VM guests are running outdated hypervisor (qemu) binaries on this host.<br>
</p></pre><br>
I tried again and the failures persisted.<p></p>
<pre>
Restarting services...
 /etc/needrestart/restart.d/dbus.service
 systemctl restart networkd-dispatcher.service systemd-logind.service unattended-upgrades.service user@1000.service
Job for user@1000.service failed because the control process exited with error code.
See &quot;systemctl status user@1000.service&quot; and &quot;journalctl -xeu user@1000.service&quot; for details.

No containers need to be restarted.

No user sessions are running outdated binaries.

No VM guests are running outdated hypervisor (qemu) binaries on this host.
</pre>
<p>The fix for the issues above was to simply reboot the EC2 instance: <code>sudo reboot</code></p>
</details>
<h3 id="32-auxilliary-tools-setup">3.2. Auxilliary Tools Setup</h3>
<pre><code class="language-bash"># Install net-tools (for later use of netcat)
sudo apt install net-tools -y

# Install zip (for later use of zip)
sudo apt install zip -y

# Install tree (for later use)
sudo apt install tree -y

# Install dependencies
# Install NGINX
sudo apt install nginx -y
-&gt; nginx is already the newest version (1.18.0-6ubuntu14.3).

# Check installed version
nginx -v
-&gt; nginx version: nginx/1.18.0 (Ubuntu)

# Check listening ports
sudo netstat -pant | grep nginx
-&gt; tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      498/nginx: master p
-&gt; tcp6       0      0 :::80                   :::*                    LISTEN      498/nginx: master p

# Install certbot
sudo apt install certbot python3-certbot -y

# Check installed version
certbot --version
certbot 1.21.0

# View the firewall list
sudo ufw app list
Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS
  OpenSSH

# Allow HTTP and HTTPS connections for nginx
sudo ufw allow &apos;Nginx Full&apos;
-&gt; Rules updated
-&gt; Rules updated (v6)

# Check the firewall status
sudo ufw status
Status: inactive

# Enable it if inactive
sudo ufw enable

# Check that it has been enabled
sudo ufw status verbose

# Install MySQL
sudo apt-get install mysql-server -y

# Check installed version
mysql --version
-&gt; mysql  Ver 8.0.32-0ubuntu0.22.04.2 for Linux on x86_64 ((Ubuntu))

# Check listening ports
sudo netstat -pant | grep mysqld
-&gt; tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      1583/mysqld
-&gt; tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      1583/mysqld

# Add the NodeSource APT repository for Node 16
curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash

# Install Node.js
sudo apt-get install nodejs -y 

# Check installed version
node --version &amp;&amp; npm --version
-&gt; v16.20.0
-&gt; 8.19.4

# List currently installed global &apos;npm&apos; packages
npm list -g
/usr/lib
&#x251C;&#x2500;&#x2500; corepack@0.17.0
&#x2514;&#x2500;&#x2500; npm@8.19.4

# Install Ghost-CLI globally
sudo npm install ghost-cli@latest -g

# Check installed version
ghost --version
-&gt; Ghost-CLI version: 1.24.0

# Confirm the package was globally installed
npm list -g
/usr/lib
&#x251C;&#x2500;&#x2500; corepack@0.17.0
&#x251C;&#x2500;&#x2500; ghost-cli@1.24.0
&#x2514;&#x2500;&#x2500; npm@8.19.4
</code></pre>
<details>
    <summary><strong>Note</strong> regarding<code>sudo npm install ghost-cli@latest -g</code></summary>
<pre>
# Install Ghost-CLI globally
sudo npm install ghost-cli@latest -g 
</pre>
<p>The <code>sudo</code> prefix is necessary because <code>npm</code> will need write access to the system-wide <code>node_modules/</code> path which is only writable by users with <code>sudo</code> privileges as can be seen below.</p>
<pre>
# the system-wide `node_modules/` path can be found using `npm root -g`
npm root -g 
-&gt; /usr/lib/node_modules

# this path is only writable by root (and users with `sudo` privileges)
ls -l /usr/lib/node_modules/
-&gt; drwxr-xr-x 4 root root 4096 May  2 19:28 corepack
-&gt; drwxr-xr-x 7 root root 4096 May  2 19:28 npm
</pre>
</details>
<h3 id="33-install-ghost">3.3. Install Ghost</h3>
<pre><code class="language-bash"># Create Ghost install path: 
sudo mkdir -p /var/www/ghost

# The folder is still owned by &apos;root&apos;
ll /var/www/ghost/
-&gt; drwxr-xr-x 2 root root 4096 May  2 20:24 ./
-&gt; drwxr-xr-x 4 root root 4096 May  2 20:24 ../

# Change the owner to our sudoer &apos;ghost-mgr&apos;
sudo chown ghost-mgr:ghost-mgr /var/www/ghost

# Set the correct permissions
sudo chmod 775 /var/www/ghost

# Confirm the folder permissions were updated
ll /var/www/ghost/
-&gt; drwxrwxr-x 2 ghost-mgr ghost-mgr 4096 May  2 20:24 ./
-&gt; drwxr-xr-x 4 root      root      4096 May  2 20:24 ../

# Change to the folder
cd /var/www/ghost

# Install Ghost with one final, non-interactive command
ghost install --no-setup
-&gt;
&#x2714; Checking system Node.js version - found v16.20.0
&#x2714; Checking current folder permissions
&#x2714; Checking memory availability
&#x2714; Checking free space
&#x2714; Checking for latest Ghost version
&#x2714; Setting up install directory
&#x2714; Downloading and installing Ghost v5.46.1
&#x2714; Finishing install process
</code></pre>
<h3 id="34-mysql-setup">3.4 MySQL Setup</h3>
<p>3.4.1. We will use the following configuration with the MySQL database server</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>DB host</td>
<td><code>localhost</code></td>
</tr>
<tr>
<td>DB user</td>
<td><code>ghost_dba</code></td>
</tr>
<tr>
<td>DB name</td>
<td><code>ghost_production</code></td>
</tr>
<tr>
<td>DB pswd</td>
<td><code>&lt;ghost-mysql-password&gt;</code></td>
</tr>
</tbody>
</table>
<p>3.4.2. Create a MySQL DBA account specifically for Ghost<br>
The MySQL DBA account <code>root</code> has no password set, so create a DBA user exclusively for Ghost</p>
<pre><code class="language-sql">mkdir -p /tmp/ghost/mysql/ &amp;&amp; cd /tmp/ghost/mysql/
cat &lt;&lt;EOF &gt; dba.sql
CREATE USER &apos;ghost_dba&apos;@&apos;localhost&apos; IDENTIFIED WITH mysql_native_password BY &apos;&lt;ghost-mysql-password&gt;&apos;;  
GRANT ALL PRIVILEGES ON *.* TO &apos;ghost_dba&apos;@&apos;localhost&apos;;
FLUSH PRIVILEGES;
quit
EOF

sudo mysql &lt; /tmp/ghost/mysql/dba.sql &gt; /tmp/ghost/mysql/dba.out
</code></pre>
<h3 id="35-ghost-config">3.5. Ghost Config</h3>
<pre><code class="language-bash"># Change back to the Ghost install path
cd /var/www/ghost

# The docs is wrong on usage of the &apos;--log&apos; parameter. It says --log &apos;[&quot;stdout&quot;, &quot;file&quot;]&apos; but what works in v5.x is below
ghost config --url https://ghost.example.com --ip 127.0.0.1 --port 2368 --log &quot;stdout&quot; --log &quot;file&quot; --db mysql --dbhost localhost --dbname ghost_production --dbuser ghost_dba --dbpass &lt;ghost-mysql-password&gt;

# We will temporarily use &apos;localhost&apos; instead of &apos;ghost.example.com&apos; to avoid exposing &apos;/ghost/&apos; admin page on the Internet
ghost config --url http://localhost:2368 --ip 127.0.0.1 --port 2368 --log &quot;stdout&quot; --log &quot;file&quot; --db mysql --dbhost localhost --dbname ghost_production --dbuser ghost --dbpass &lt;ghost-mysql-password&gt;


# This will create a `config.production.json` in /var/www/ghost which you can review
cat /var/www/ghost/config.production.json
{
  &quot;url&quot;: &quot;http://localhost&quot;,
  &quot;server&quot;: {
    &quot;port&quot;: 2368,
    &quot;host&quot;: &quot;127.0.0.1&quot;
  },
  &quot;database&quot;: {
    &quot;client&quot;: &quot;mysql&quot;,
    &quot;connection&quot;: {
      &quot;host&quot;: &quot;localhost&quot;,
      &quot;user&quot;: &quot;ghost_dba&quot;,
      &quot;password&quot;: &quot;&lt;ghost-mysql-password&gt;&quot;,
      &quot;database&quot;: &quot;ghost_production&quot;
    }
  },
  &quot;mail&quot;: {
    &quot;transport&quot;: &quot;Direct&quot;
  },
  &quot;logging&quot;: {
    &quot;transports&quot;: [
      &quot;file&quot;,
      &quot;stdout&quot;
    ]
  },
  &quot;process&quot;: &quot;systemd&quot;,
  &quot;paths&quot;: {
    &quot;contentPath&quot;: &quot;/var/www/ghost/content&quot;
  }
}
</code></pre>
<blockquote>
<p><strong>Note</strong><br>
specifying <code>ghost config --url http://127.0.0.1:2368 ...</code> will create a <code>config.development.json</code> instead of <code>config.production.json</code> which is why I had to use the production URL <a href="https://ghost.example.com/?ref=ayewo.com">https://ghost.example.com</a> in ghost config.</p>
</blockquote>
<h3 id="36-ghost-setup">3.6. Ghost Setup</h3>
<p>3.6.1. Finish setting up Ghost non-interactively:</p>
<pre><code class="language-bash"># setup (will print the ghost admin URL to the console)
ghost setup --no-prompt --no-setup-mysql -V


# check the service is up
sudo netstat -pant | grep node
-&gt; tcp        0      0 127.0.0.1:2368          0.0.0.0:*               LISTEN      2762/node
-&gt; tcp        0      0 127.0.0.1:32904         127.0.0.1:3306          ESTABLISHED 2762/node
-&gt; tcp        0      0 127.0.0.1:32890         127.0.0.1:3306          ESTABLISHED 2762/node
</code></pre>
<p>There are two ways by which you can complete the Ghost admin user setup process:</p>
<ul>
<li>remotely from your browser using <a href="https://goteleport.com/blog/ssh-tunneling-explained/?ref=ayewo.com">SSH tunneling</a> or;</li>
<li>locally from the CLI using <code>curl</code><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>.</li>
</ul>
<p>3.6.2. Open another Terminal tab and connect to Ghost on localhost using SSH tunneling:</p>
<pre><code class="language-bash">ssh -i ~/.ssh/ec2-keypair.pem -v -L 2368:localhost:2368 ubuntu@ghost.example.com
</code></pre>
<p>Next visit <code>localhost:2368</code> on your browser to complete the setup process:</p>
<pre><code class="language-bash">open http://localhost:2368/ghost/#/setup 
</code></pre>
<p>3.6.3. Or, use the following <code>curl</code> command to achieve the same thing from the CLI:</p>
<pre><code class="language-bash">curl &apos;http://localhost:2368/ghost/api/admin/authentication/setup/&apos; \
-X &apos;POST&apos; \
-H &apos;Content-Type: application/json; charset=UTF-8&apos; \
-H &apos;Accept: application/json, text/javascript, */*; q=0.01&apos; \
-H &apos;Host: localhost:2368&apos; \
-H &apos;Origin: http://localhost:2368&apos; \
-H &apos;User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15&apos; \
-H &apos;Referer: http://localhost:2368/ghost/&apos; \
-H &apos;Connection: keep-alive&apos; \
-H &apos;X-Requested-With: XMLHttpRequest&apos; \
-H &apos;App-Pragma: no-cache&apos; \
--data-binary &apos;{&quot;setup&quot;:[{&quot;name&quot;:&quot;Admin&quot;,&quot;email&quot;:&quot;email@ghost.example.com&quot;,&quot;password&quot;:&quot;&lt;ghost-admin-password&gt;&quot;,&quot;blogTitle&quot;:&quot;Blog Title&quot;}]}&apos;
</code></pre>
<blockquote>
<p>{&quot;users&quot;:[{&quot;<a href="mailto:id%22:%221%22,%22name%22:%22Admin%22,%22slug%22:%22admin%22,%22email%22:%22email@ghost.example.com">id&quot;:&quot;1&quot;,&quot;name&quot;:&quot;Admin&quot;,&quot;slug&quot;:&quot;admin&quot;,&quot;email&quot;:&quot;email@ghost.example.com</a>&quot;,&quot;profile_image&quot;:null,&quot;cover_image&quot;:null,&quot;bio&quot;:null,&quot;website&quot;:null,&quot;location&quot;:null,&quot;facebook&quot;:null,&quot;twitter&quot;:null,&quot;accessibility&quot;:null,&quot;status&quot;:&quot;active&quot;,&quot;meta_title&quot;:null,&quot;meta_description&quot;:null,&quot;tour&quot;:null,&quot;last_seen&quot;:null,&quot;comment_notifications&quot;:true,&quot;free_member_signup_notification&quot;:true,&quot;paid_subscription_started_notification&quot;:true,&quot;paid_subscription_canceled_notification&quot;:false,&quot;mention_notifications&quot;:true,&quot;milestone_notifications&quot;:true,&quot;created_at&quot;:&quot;2023-06-14T15:49:42.000Z&quot;,&quot;updated_at&quot;:&quot;2023-06-17T07:58:16.000Z&quot;,&quot;url&quot;:&quot;<a href="https://ghost.example.com/author/admin/?ref=ayewo.com">https://ghost.example.com/author/admin/</a>&quot;}]}</p>
</blockquote>
<p>Remember to change the <code>email</code>, <code>password</code>, <code>blogTitle</code> in the <code>curl</code> command accordingly to match your desired values.</p>
<p>3.6.4. Now that an admin user has been set, switch to a production URL</p>
<pre><code class="language-bash"># First stop Ghost
ghost stop

# Update the production URL
ghost config --url https://ghost.example.com

# Your config should now look identical to the following output
cat /var/www/ghost/config.production.json
{
  &quot;url&quot;: &quot;https://ghost.example.com&quot;,
  &quot;server&quot;: {
    &quot;port&quot;: 2368,
    &quot;host&quot;: &quot;127.0.0.1&quot;
  },
  &quot;database&quot;: {
    &quot;client&quot;: &quot;mysql&quot;,
    &quot;connection&quot;: {
      &quot;host&quot;: &quot;localhost&quot;,
      &quot;user&quot;: &quot;ghost_dba&quot;,
      &quot;password&quot;: &quot;&lt;ghost-mysql-password&gt;&quot;,
      &quot;database&quot;: &quot;ghost_production&quot;
    }
  },
  &quot;mail&quot;: {
    &quot;transport&quot;: &quot;Direct&quot;
  },
  &quot;logging&quot;: {
    &quot;transports&quot;: [
      &quot;file&quot;,
      &quot;stdout&quot;
    ]
  },
  &quot;process&quot;: &quot;systemd&quot;,
  &quot;paths&quot;: {
    &quot;contentPath&quot;: &quot;/var/www/ghost/content&quot;
  }
}
</code></pre>
<p>3.6.5. Start Ghost</p>
<pre><code class="language-bash">ghost start

+ sudo systemctl is-active ghost_localhost
&#x2714; Checking system Node.js version - found v16.20.0
&#x2714; Ensuring user is not logged in as ghost user
&#x2714; Checking if logged in user is directory owner
&#x2714; Checking current folder permissions
+ sudo systemctl is-active ghost_localhost
&#x2714; Validating config
&#x2714; Checking folder permissions
&#x2714; Checking file permissions
&#x2714; Checking content folder ownership
&#x2714; Checking memory availability
&#x2714; Checking binary dependencies
&#x2714; Checking systemd unit file
&#x2714; Checking systemd node version - found v16.20.0
+ sudo systemctl start ghost_localhost
+ sudo systemctl is-enabled ghost_localhost
&#x2714; Starting Ghost: localhost

------------------------------------------------------------------------------

Your admin interface is located at:

    https://ghost.example.com/ghost/

# check the service is up
sudo netstat -pant | grep node
</code></pre>
<p>3.6.6. View your brand new Ghost blog</p>
<pre><code class="language-bash">open https://ghost.example.com/ghost/
</code></pre>
<h2 id="4-tidy-up">4. Tidy Up</h2>
<p>4.1. Clean up on the server</p>
<pre><code class="language-bash"># check disk usage 
cd /home/ghost-mgr/ &amp;&amp; du -shL .cache/
-&gt; 819M  .cache/

# free up some space
ghost buster

# remove scratch folder
rm -rf /tmp/ghost
</code></pre>
<p>4.2. Clean up locally</p>
<pre><code class="language-bash"># Remove the temporary mapping of the Elastic IP
sudo vim /etc/hosts

# check that the domain is now being resolved correctly
nslookup ghost.example.com
</code></pre>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>For almost 4 years, no definitive answer has been given to the question on how to <a href="https://forum.ghost.org/t/creating-admin-user-programmatically/8952?ref=ayewo.com">create the admin user programmatically</a> so I knew I had to figure it out so I could completely automate the process of launching a Ghost blog. I show how to <a href="https://ayewo.com/programmatic-creation-of-the-ghost-admin-user/">[:create the admin programmatically using <code>curl</code>]</a> in a separate post. Compared to WordPress, Ghost&apos;s DevOps story isn&apos;t particularly great and I&apos;m hoping <code>ghost.sh</code> will slowly help fix that. <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Ghost.sh]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>This article is the umbrella article for a 3-part series on how move (and upgrade) a <em>live</em> Ghost blog hosted on a DigitalOcean droplet to an EC2 VM on AWS, with minimal downtime.</p>
<p>Other articles planned/published are:</p>
<ul>
<li><a href="https://ayewo.com/how-to-host-a-new-ghost-blog-on-aws/">Quick and Easy Hosting of a Ghost Blog on AWS using EC2</a></li></ul>]]></description><link>https://ayewo.com/ghost-sh/</link><guid isPermaLink="false">64a486403b652d31711a987a</guid><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Sat, 01 Jul 2023 10:50:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>This article is the umbrella article for a 3-part series on how move (and upgrade) a <em>live</em> Ghost blog hosted on a DigitalOcean droplet to an EC2 VM on AWS, with minimal downtime.</p>
<p>Other articles planned/published are:</p>
<ul>
<li><a href="https://ayewo.com/how-to-host-a-new-ghost-blog-on-aws/">Quick and Easy Hosting of a Ghost Blog on AWS using EC2 (and RDS)</a>;</li>
<li><a href="https://ayewo.com/programmatic-creation-of-the-ghost-admin-user/">Programmatic Creation of the Ghost Admin User</a></li>
<li>Easy Migration of a Ghost Blog Between Cloud Providers using Terraform</li>
</ul>
<p>Before I dive in, I&apos;d like to go on a small detour on what motivated this project.</p>
<h2 id="ghosts-adoption-is-growing-but-theres-still-friction">Ghost&apos;s Adoption is Growing but There&apos;s Still Friction</h2>
<p><a href="https://ghost.org/?ref=ayewo.com">Ghost</a> is now at version 5.x and their <a href="https://ghost.org/docs/?ref=ayewo.com">documentation</a> story has improved, but unfortunately, their docs are still lacking on real world information on self-hosting like: how to migrate between hosts, or the 1-Click launch of a new blog, from start to finish, on any cloud provider.</p>
<p>Information on how to do any of those things is scattered across forum posts, GitHub and random blogs. I&apos;m hoping my publication of this information in one place with save you all the hair-pulling I had to endure to get this working.</p>
<p>(The blog you are currently reading was simultaneously <em>upgraded</em> from Ghost v3.x to v5.x and <em>moved</em> from DigitalOcean to AWS.)</p>
<h3 id="they-select-for-folks-with-ability-to-pay">They Select for Folks with Ability to Pay</h3>
<p>Ghost is a COSS (Commercial Open Source Software) project&#x2014;it is completely open source and free to use, and if you choose to self-host it, the amount of friction you will face is no accident.</p>
<p>The project&apos;s way of making money is by offering a paid service called Ghost(Pro) designed to remove friction for both blogging <em>first-timers</em> and blogging <em>veterans</em> looking to switch to Ghost.</p>
<p>Ghost(Pro) is essentially a DFY (Done For You) service that handles <a href="https://ghost.org/concierge?ref=ayewo.com">blog migration</a> and blog hosting for 4 different personas, which is why there are <a href="https://ghost.org/pricing/?ref=ayewo.com">4 different price</a> points. (Pre-pandemic there were only <a href="http://web.archive.org/web/20200301023037/https://ghost.org/pricing/">3 price points</a>.)</p>
<p>From the outside, it seems Ghost is squarely focused on attracting users with a high likelihood of converting into paying customers in the future, albeit to the detriment of others. This is a reasonable stance for a small team.</p>
<p>But, this stance has a glaring casualty: it limits broader adoption of Ghost as an OSS project.</p>
<p>Highly technical users i.e. technical end users with more time on their hands than money (hi! &#x1F44B;, that&apos;s me &#x1F601;) who adopt Ghost and self-host it end up paying with a huge chunk of their time<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup><sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup> because the project is not (yet) interested in eliminating friction for self-hosters since they don&apos;t contribute <em>directly</em> to their bottomline. Perhaps they hope the community will rise up to fill the gap?</p>
<h3 id="they-reduce-friction-for-first-timers">They Reduce Friction for First-Timers</h3>
<p>To their credit, the Ghost team maintains a 1-Click app on the <a href="https://marketplace.digitalocean.com/apps/ghost?ref=ayewo.com">DigitalOcean marketplace</a> for <em>first-timers</em> looking for a quick and easy way to self-host Ghost.</p>
<p>In fact, this is how I got started with Ghost in 2020, and I&apos;ve been on this setup for 2+ years.</p>
<h3 id="they-ignore-friction-for-veterans">They Ignore Friction for Veterans</h3>
<p>The 1-Click app caters quite well to the needs of <em>first-time</em> users seeking to launch a <em>brand new</em> blog. But, it is woefully inadequate if you are not a <em>first-timer</em>:</p>
<ul>
<li>the 1-Click app or <code>ghost-cli</code> are often not enough<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup> to upgrade an <em>existing</em> blog;</li>
<li>the 1-Click app itself is not available outside of the DigitalOcean marketplace and thus cannot be used on other cloud providers like AWS, Azure or GCP;</li>
<li>the 1-Click app does not have support for importing or migrating an <em>existing</em> blog to DigitalOcean, or indeed to any of the other cloud providers.</li>
</ul>
<p>Regarding the first point, I&apos;ll share two instances from my experience.</p>
<h3 id="theres-a-lot-of-housekeeping-to-keep-ghost-up-to-date">There&apos;s a lot of Housekeeping to keep Ghost up-to-date</h3>
<h4 id="first-example-things-get-out-of-sync-fairly-quickly">First Example: Things get out of Sync Fairly Quickly</h4>
<p>Back in December 2020, I used the 1-Click app to stand up the latest version of Ghost at the time (v3.38.3) to host <a href="https://ayewo.com/">https://ayewo.com/</a> on a $5/mo droplet on DigitalOcean.</p>
<p>By November 2021, Ghost was now on v4.x.x. When I tried to perform an upgrade from v3.38.3 to v4.x.x, I was met with this inscrutable error (at the time):</p>
<pre><code class="language-bash">ghost update

You are running an outdated version of Ghost-CLI.
It is recommended that you upgrade before continuing.
Run `npm install -g ghost-cli@latest` to upgrade.

+ sudo systemctl is-active ghost_ayewo-com
&#x2714; Checking system Node.js version
&#x2714; Ensuring user is not logged in as ghost user
&#x2714; Checking if logged in user is directory owner
&#x2714; Checking current folder permissions
&#x2714; Checking folder permissions
&#x2716; Checking file permissions
&#x2714; Checking content folder ownership
&#x2714; Checking memory availability
&#x2714; Checking free space
One or more errors occurred.

1) Checking file permissions

Message: Your installation folder contains some directories or files with incorrect permissions:
- ./content/themes/Alto-master/assets/js/lib/owl.carousel.min.js
- ./content/themes/Alto-master/assets/js/lib/jquery.slicknav.min.js
- ./content/themes/Alto-master/assets/js/lib/jquery.fitvids.js
- ./content/themes/Alto-master/assets/fonts/Alto.woff
- ./content/themes/Alto-master/assets/fonts/selection.json
- ./content/themes/Alto-master/assets/fonts/Alto.svg
- ./content/themes/Alto-master/assets/fonts/Alto.ttf
Run sudo find ./ ! -path &quot;./versions/*&quot; -type f -exec chmod 664 {} \; and try again.

Debug Information:
    OS: Ubuntu, v18.04.4 LTS
    Node Version: v12.18.0
    Ghost Version: 3.38.3
    Ghost-CLI Version: 1.15.2
    Environment: production
    Command: &apos;ghost update&apos;

Try running ghost doctor to check your system for known issues.

You can always refer to https://ghost.org/docs/api/ghost-cli/ for troubleshooting.
</code></pre>
<p>Of course the error was due to a file permission issue as hinted in the middle of the output, but realizing this required familiarity with Ghost&apos;s deployment that I didn&apos;t yet have.</p>
<p>(The file permissions issue must have arisen silently when I tried out the <a href="https://github.com/TryGhost/Alto?ref=ayewo.com">Alto theme</a> a while back. I uploaded it from the Ghost Admin page, applied it site-wide but didn&apos;t like how it looked, so I switched back to the default theme (Casper) and promptly forgot to delete its files from my server.)</p>
<h4 id="second-example-breaking-changes">Second Example: Breaking Changes</h4>
<p>Ghost v5.x introduced a <a href="https://forum.ghost.org/t/when-will-mysql-8-be-mandated-which-ver/29228/2?ref=ayewo.com">breaking change</a> where support for <a href="https://ghost.org/docs/update-major-version/?ref=ayewo.com#check-mysql-version">MySQL versions below v8.x was dropped</a>. The <a href="https://ghost.org/docs/faq/supported-databases/?ref=ayewo.com">official docs</a> on how to do the migration had suggestions that boiled down to taking the <a href="https://ghost.org/docs/faq/supported-databases/?ref=ayewo.com#how-to-update-mysql-5-to-mysql-8">server offline to do an OS upgrade before applying some essential DDL changes</a>.</p>
<p>I didn&apos;t bother with the official docs due to the uncertainty involved in an upgrade to the latest Ubuntu Linux version. How much downtime am I looking at? Plus the follow-up DDL changes looked like open heart surgery, if I got it wrong.</p>
<p>They did helpfully link to an <a href="https://forum.ghost.org/t/how-to-migrate-from-mariadb-10-to-mysql-8/29575?ref=ayewo.com">alternative approach that minimized server downtime</a> but I couldn&apos;t use it because my goal was to upgrade Ghost <em>and</em> move hosts to a different cloud provider.</p>
<p>This second example is a mixed bag. The Ghost project is slowly maturing which is good, but they could do a better job of making breaking changes easy to deal with.</p>
<h3 id="pets-vs-cattle">Pets vs Cattle</h3>
<p>The instructions certainly got me thinking: if we flipped our thinking by treating the server used for the deployment like <a href="https://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/?ref=ayewo.com">cattle instead of pets</a>, the whole migration experience could be:</p>
<ul>
<li>simplified and;</li>
<li>automated.</li>
</ul>
<h2 id="reducing-friction-for-first-timers-veterans">Reducing Friction for First-Timers &amp; Veterans</h2>
<p>The <code>ghost.sh</code><sup class="footnote-ref"><a href="#fn4" id="fnref4">[4]</a></sup> project is intended to plug those gaps: collect the most common step-by-step instructions necessary to launch a Ghost blog (whether brand new or existing) and turn those instructions into a 1-Click script that can be used on any cloud provider.</p>
<p>The project is open source: <a href="https://github.com/ayewo/ghost.sh?ref=ayewo.com"><code>ghost.sh</code></a>.</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>For instance, see this recent <a href="https://news.ycombinator.com/item?id=36660199&amp;ref=ayewo.com">HN comment thread</a> where the following frustrations were shared: <em>&quot;As someone who&apos;s manually setup Ghost blog, I&apos;d heavily advise towards just paying for Ghost hosting. I wasted hours setting it up, and I gave up when it came time to configure emailing.&quot;</em> <a href="#fnref1" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn2" class="footnote-item"><p>Another comment from the same thread:  <em>&quot;I had a heck of a time setting it up to self host myself and I am technically inclined. There was one thing misconfigured by default that took me a few hours to catch. Now I&#x2019;m trying to figure out why the VM stops responding randomly&#x2026;free sounded better to me at the time&quot;</em> <a href="#fnref2" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn3" class="footnote-item"><p>There are some neat aspects of the <code>ghost-cli</code> that I learned of while working on this project, but overall, I think there&apos;s still room for more community participation. WordPress has had <a href="https://wordpress.org/documentation/article/configuring-automatic-background-updates/?ref=ayewo.com">automatic background updates</a> since <a href="https://wordpress.org/documentation/wordpress-version/version-3-7/?ref=ayewo.com">v3.7</a> (released in 2013) and there are a handful of <a href="https://github.com/topics/wordpress-autoinstall?ref=ayewo.com">community efforts</a> to automate the installation of WordPress (e.g. <a href="https://github.com/AbhishekGhosh/Ubuntu-18.04-WordPress-Autoinstall-Bash-Script/blob/f7ac17c26ad6a74116dd74618a5604095ee91837/beta.sh?ref=ayewo.com"><code>AbhishekGhosh/Ubuntu-18.04-WordPress-Autoinstall-Bash-Script</code></a>, in addition multiple hosting providers offering 1-Click installation of WordPress. The few automation-focused community projects for Ghost that I came across were either archived (e.g. <a href="https://github.com/sirredbeard/systemd-ghost?ref=ayewo.com"><code>systemd-ghost</code></a>), abandoned (e.g. <a href="https://github.com/cobyism/ghost-on-heroku?ref=ayewo.com"><code>ghost-on-heroku</code></a>), or too brittle for production use (e.g. <a href="https://github.com/paladini/ghost-on-github-pages?ref=ayewo.com"><code>ghost-on-github-pages </code></a>) or requires quite a few steps to get started (e.g. <a href="https://github.com/ethibox/awesome-stacks/tree/999ca5d71e0fd415e37a7542f30fccc849675bae?ref=ayewo.com#get-started"><code>ethibox/awesome-stacks</code></a>). Although <a href="https://github.com/bdeitte/ghost-on-aws?ref=ayewo.com"><code>ghost-on-aws</code></a> doesn&apos;t provide code and has not been updated in 3 years, I still found the high-level instructions to be useful as a checklist when planning to deploy Ghost to AWS. <a href="#fnref3" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
<li id="fn4" class="footnote-item"><p>Originally named the project <em>Ghost(Ultra)</em> since Ultra is the <a href="https://en.wikipedia.org/wiki/Apple_M1?ref=ayewo.com#Products_that_use_the_Apple_M1_series">next step</a> (hypothetically) after <em>Ghost</em>, <em>Ghost(Pro)</em>, <em>Ghost(Max)</em>. After writing out a dozen names, landed on the much neater Ghost.sh. The lesson is to write out a dozen names before settling on a name. <a href="#fnref4" class="footnote-backref">&#x21A9;&#xFE0E;</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown--><p></p><p></p><h3></h3>]]></content:encoded></item><item><title><![CDATA[Make sudo Spiffy on macOS with TouchID]]></title><description><![CDATA[<p>About five months ago, I followed the steps in this article: <a href="https://it.digitaino.com/use-touchid-to-authenticate-sudo-on-macos/?ref=ayewo.com">Use TouchID to Authenticate sudo on macOS</a> via <a href="https://news.ycombinator.com/item?id=32611340&amp;ref=ayewo.com">HN</a> so that I could use my fingerprint to complete actions that require <code>sudo</code> privileges.</p><p>Other than using TouchID to login at the start of my day, I rarely use the</p>]]></description><link>https://ayewo.com/sudo-on-macos-with-touchid/</link><guid isPermaLink="false">6499621cf941aa2920349d22</guid><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Sat, 21 Jan 2023 15:55:48 GMT</pubDate><media:content url="https://ayewo.com/content/images/2023/01/SCR-20230121-ndq-2.png" medium="image"/><content:encoded><![CDATA[<img src="https://ayewo.com/content/images/2023/01/SCR-20230121-ndq-2.png" alt="Make sudo Spiffy on macOS with TouchID"><p>About five months ago, I followed the steps in this article: <a href="https://it.digitaino.com/use-touchid-to-authenticate-sudo-on-macos/?ref=ayewo.com">Use TouchID to Authenticate sudo on macOS</a> via <a href="https://news.ycombinator.com/item?id=32611340&amp;ref=ayewo.com">HN</a> so that I could use my fingerprint to complete actions that require <code>sudo</code> privileges.</p><p>Other than using TouchID to login at the start of my day, I rarely use the TouchID button on my MacBook Pro for much else, so I liked the added convenience this feature offered.</p><p>I have been putting off updating to macOS Ventura for several weeks now, but I made a mental note: whenever I do succumb to doing an <em>major</em> OS update, I would re-apply my TouchID changes because such changes do not survive a <em>major</em> OS update.</p><p>Fast-forward to today and I only just realized that my changes had been overwritten. At the time I enabled TouchID with <code>sudo</code> on August 27, 2022, I was on macOS Monterey 12.3.1. About a month later, on September 26, I updated to Monterey 12.6. </p><p>It turns out that, even for a <a href="https://news.ycombinator.com/item?id=32611734&amp;ref=ayewo.com"><em>minor</em> OS update</a>, those changes will be overwritten by macOS. I had incorrectly assumed such changes will only be overwritten by a <em>major</em> update i.e. moving from Big Sur to Monterey or Monterey to Ventura.</p><p>I&apos;m still trying to come up with a safe and repeatable way to <em>automatically</em> apply these changes after an OS update, but for the time being, I&apos;m simply going to spell out all the steps in this article so I can quickly copy-paste just the bits that I need for my future self.</p><p><strong>Step 1:</strong> view the contents of <code>/etc/pam.d/sudo</code>:</p><!--kg-card-begin: markdown--><pre><code class="language-bash">cat /etc/pam.d/sudo
# sudo: auth account password session
auth       sufficient     pam_smartcard.so
auth       required       pam_opendirectory.so
account    required       pam_permit.so
password   required       pam_deny.so
session    required       pam_permit.so

</code></pre>
<!--kg-card-end: markdown--><p><strong>Step 2:</strong> make a backup of <code>/etc/pam.d/sudo</code> so any typos can easily be reverted (ask me how I know &#x1F926;&#x200D;&#x2642;&#xFE0F;). Note that macOS wont allow you write to <code>/etc/</code> so choose a location that is writable like <code>/Users/&lt;account&gt;</code> :</p><!--kg-card-begin: markdown--><pre><code class="language-bash">cp /etc/pam.d/sudo ~/etc-pam.d-sudo
</code></pre>
<!--kg-card-end: markdown--><p><strong>Step 3:</strong> add the following line: <code>auth sufficient pam_tid.so</code> directly below the first line <code>auth sufficient pam_smartcard.so</code> inside the file <code>/etc/pam.d/sudo</code>:</p><!--kg-card-begin: markdown--><pre><code class="language-bash"># even though it&apos;s a small edit, doing it manually is error-prone
sudo vim /etc/pam.d/sudo 
</code></pre>
<!--kg-card-end: markdown--><p>or, if you are lazy like me, use this <a href="https://news.ycombinator.com/item?id=32617051&amp;ref=ayewo.com">one-liner</a> instead:</p><!--kg-card-begin: markdown--><pre><code class="language-bash"># this one-liner is much safer
sudo perl -pi -e &apos;s/(pam_smartcard.so)/$1\nauth       sufficient     pam_tid.so/&apos; /etc/pam.d/sudo
</code></pre>
<!--kg-card-end: markdown--><p><strong>Step 4:</strong> review the updated file <code>/etc/pam.d/sudo</code> to confirm your changes:</p><!--kg-card-begin: markdown--><pre><code class="language-bash">cat /etc/pam.d/sudo
# sudo: auth account password session
auth       sufficient     pam_smartcard.so
auth       sufficient     pam_tid.so
auth       required       pam_opendirectory.so
account    required       pam_permit.so
password   required       pam_deny.so
session    required       pam_permit.so
</code></pre>
<!--kg-card-end: markdown--><p>Done!</p><h1></h1>]]></content:encoded></item><item><title><![CDATA[An Obscure Error While Downloading a YouTube Video for Offline Viewing]]></title><description><![CDATA[Trying to download a video from YouTube for offline use using youtube-dl and it keeps failing? This article will show you how I fixed it.]]></description><link>https://ayewo.com/how-to-fix-a-failing-offline-download-using-youtube-dl/</link><guid isPermaLink="false">6499621cf941aa2920349d21</guid><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Thu, 22 Dec 2022 21:56:30 GMT</pubDate><media:content url="https://ayewo.com/content/images/2022/12/SCR-20221222-youtube-dl-search-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://ayewo.com/content/images/2022/12/SCR-20221222-youtube-dl-search-1.png" alt="An Obscure Error While Downloading a YouTube Video for Offline Viewing"><p>Lenny Rachitsky recently did a <a href="https://twitter.com/lennysan/status/1600591639982272512?ref=ayewo.com">lengthy</a> podcast interview with <a href="https://twitter.com/ethan_l_s?ref=ayewo.com">Ethan Smith</a> who runs <a href="https://www.graphitehq.com/?ref=ayewo.com">Graphite</a>, an SEO agency.</p><p>The video is more than 1:30 hrs long so I decided I was going to watch it offline. Several attempts to download the video from YouTube failed but I managed to find a workaround.</p><h2 id="the-download-failed-on-macos-and-ubuntu">The Download Failed on macOS and Ubuntu</h2><p>My first attempt at downloading the video on macOS using <em><a href="https://yt-dl.org/?ref=ayewo.com"><strong>youtube-dl</strong></a> </em>led to the following error:</p><blockquote>[download] &#xA0; 0.4% of 645.08MiB at 51.43KiB/s ETA 03:33:17[download] Got server HTTP error: Downloaded 2451832 bytes, expected 676416237 bytes. Retrying (attempt 1 of 10)...</blockquote><p>I was too impatient to try to figure out why the download failed, so I SSHed into a spare Ubuntu Linux VPS I had running on AWS and installed the <em>youtube-dl </em>binary. This second attempt also failed. The error message was identical to the error I was shown on macOS.</p><p>A quick check revealed that I had version v2021.12.17 of <em>youtube-dl</em> installed on both macOS and Ubuntu.</p><p>At the time of my attempt, the current date was 2022.12.17 which coincided with the month and day (but not the year) of release for the version I had installed (<a href="https://github.com/ytdl-org/youtube-dl/releases/tag/2021.12.17/?ref=ayewo.com">v2021.12.17</a>). In other words, the only difference was the year of release (2021 vs 2022). </p><p>Since the version of <em>youtube-dl</em> I had installed was released exactly a year ago from the current date, the quickest way to fix the issue was to download the latest version of <em>youtube-dl, </em>but it turns out that v2021.12.17 is indeed the latest version.</p><p>Then, I remembered that the open source project for <em>youtube-dl </em>had been having <a href="https://github.com/ytdl-org/youtube-dl/issues/30568?ref=ayewo.com">management issues</a>. Collectively, these issues led the community to shift their focus to an actively maintained fork called <a href="https://github.com/yt-dlp/yt-dlp?ref=ayewo.com"><em><strong>yt-dlp</strong></em></a>.</p><p>So, I downloaded the latest binary for <em>yt-dlp </em>which was released on <a href="https://github.com/yt-dlp/yt-dlp/releases/tag/2022.11.11?ref=ayewo.com">GitHub</a> only a month ago. The download also failed. The error output from <em>yt-dlp </em>was identical to the output above from <em>youtube-dl.</em></p><h2 id="slowing-down-to-diagnose-the-error">Slowing Down to Diagnose the Error</h2><p>Based on the hint contained in the error, my next suspicion was that there was some kind of mismatch between the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests?ref=ayewo.com">HTTP range request</a> that <em>youtube-dl </em>was sending and what YouTube&apos;s servers were expecting.</p><p>I condensed the error down to the following string and did a quick search on Google:</p><blockquote>Got error: Downloaded 2451832 bytes, expected 676416237 bytes.</blockquote><p>In return, Google gave the following (non-) result:</p><figure class="kg-card kg-image-card"><img src="https://ayewo.com/content/images/2022/12/SCR-20221222-youtube-dl-search.png" class="kg-image" alt="An Obscure Error While Downloading a YouTube Video for Offline Viewing" loading="lazy" width="2000" height="1070" srcset="https://ayewo.com/content/images/size/w600/2022/12/SCR-20221222-youtube-dl-search.png 600w, https://ayewo.com/content/images/size/w1000/2022/12/SCR-20221222-youtube-dl-search.png 1000w, https://ayewo.com/content/images/size/w1600/2022/12/SCR-20221222-youtube-dl-search.png 1600w, https://ayewo.com/content/images/2022/12/SCR-20221222-youtube-dl-search.png 2120w" sizes="(min-width: 720px) 720px"></figure><blockquote>About 0 results (0.44 seconds)</blockquote><p>Although there were no results for my query, Google did helpfully include 4 screenshots, two of which linked to GitHub. I clicked through to the first image which led me to this <a href="https://github.com/ytdl-org/youtube-dl/issues/14845?ref=ayewo.com">closed issue</a> on the <em>youtube-dl </em>project. </p><p>This <a href="https://github.com/ytdl-org/youtube-dl/issues/14845?ref=ayewo.com#issuecomment-346947622">comment</a>, left in 2017, provided a valuable hint: that the issue is from YouTube and not due to a bug in the <em>youtube-dl </em>code. The workaround is to use a different format.</p><p>Since I didn&apos;t specify any format in all of my attempts, I decided to skim the CLI arguments supported by <em>youtube-dl </em>so I could specify a format explicitly.</p><!--kg-card-begin: markdown--><pre><code class="language-bash">youtube-dl --help


Video Format Options:
    -f, --format FORMAT                  Video format code, see the &quot;FORMAT SELECTION&quot; for all the info
    --all-formats                        Download all available video formats
    --prefer-free-formats                Prefer free video formats unless a specific one is requested
    -F, --list-formats                   List all available formats of requested videos
</code></pre>
<!--kg-card-end: markdown--><p>Armed with this information, I turn on verbose mode (with the &quot;-v&quot; option) and passed the &quot;-F&quot; option to <em>youtube-dl</em> so it can list the video formats supported by YouTube for the video in question:</p><!--kg-card-begin: markdown--><pre><code class="language-bash">youtube-dl -v &quot;https://www.youtube.com/watch?v=mOY159TlYXM&quot; -F
[debug] System config: []
[debug] User config: []
[debug] Custom config: []
[debug] Command-line args: [&apos;-v&apos;, &apos;https://www.youtube.com/watch?v=mOY159TlYXM&apos;, &apos;-F&apos;]
[debug] Encodings: locale UTF-8, fs utf-8, out utf-8, pref UTF-8
[debug] youtube-dl version 2021.12.17
[debug] Git HEAD: fba051f98
[debug] Python version 3.10.5 (CPython) - macOS-12.6-x86_64-i386-64bit
[debug] exe versions: none
[debug] Proxy map: {}
[youtube] mOY159TlYXM: Downloading webpage
[info] Available formats for mOY159TlYXM:
... (truncated due to poor formatting) ...
... (see the next screen shot) ...
</code></pre>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://ayewo.com/content/images/2022/12/SCR-20221222-youtube-dl-formats.png" class="kg-image" alt="An Obscure Error While Downloading a YouTube Video for Offline Viewing" loading="lazy" width="1736" height="654" srcset="https://ayewo.com/content/images/size/w600/2022/12/SCR-20221222-youtube-dl-formats.png 600w, https://ayewo.com/content/images/size/w1000/2022/12/SCR-20221222-youtube-dl-formats.png 1000w, https://ayewo.com/content/images/size/w1600/2022/12/SCR-20221222-youtube-dl-formats.png 1600w, https://ayewo.com/content/images/2022/12/SCR-20221222-youtube-dl-formats.png 1736w" sizes="(min-width: 720px) 720px"></figure><p>It turns out that when I don&apos;t explicitly specify a format, <em>youtube-dl </em>defaults to downloading the last format in the list (format code: 22; file format: mp4 at a 1280x720 resolution). </p><p>It seems there&apos;s problem with how that video format is encoded/served by YouTube&apos;s servers because any attempts to download it explicitly leads to the error I mentioned above. (Highly likely that YT has checks in place to surpress encoding errors when videos are consumed normally by a human.) </p><p>Once I picked the mp4 format at a lower resolution (format code: 18, file format: mp4 at a 640x360 resolution), the download worked fine. The command I used was:</p><!--kg-card-begin: markdown--><pre><code class="language-bash">youtube-dl -v &quot;https://www.youtube.com/watch?v=mOY159TlYXM&quot; -f 18
</code></pre>
<!--kg-card-end: markdown--><p>HTH!</p>]]></content:encoded></item><item><title><![CDATA[How to get delv working on macOS]]></title><description><![CDATA[Getting a recent version of delv working on macOS is as easy as "brew install bind".]]></description><link>https://ayewo.com/how-to-get-delv-working-on-macos/</link><guid isPermaLink="false">6499621cf941aa2920349d1f</guid><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Mon, 05 Sep 2022 16:29:26 GMT</pubDate><media:content url="https://ayewo.com/content/images/2022/11/delv-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://ayewo.com/content/images/2022/11/delv-1.png" alt="How to get delv working on macOS"><p>While reading this <a href="https://news.ycombinator.com/item?id=32717945&amp;ref=ayewo.com">HN</a> comment, I learnt of a CLI tool named <code>delv</code>.</p><p><code>delv</code>&apos;s <a href="https://kb.isc.org/docs/aa-01152?ref=ayewo.com">history</a> paints it as the spiritual successor to <code>dig</code>, plus its name pays homage to a popular nursey rhyme. Interesting.</p><p>Similar to <code>dig</code> ,<a href="https://kb.isc.org/docs/aa-01152?ref=ayewo.com"><code>delv</code></a> is a DNS lookup and validation utility. Apple shipped the <code>delv</code> binary with my machine running macOS Monterey (v12.3.1) but I had trouble getting it to work.</p><p>When I ran the first command in that HN comment: <code>delv MX ycombinator.com @9.9.9.9</code> I got an inscrutable output:</p><!--kg-card-begin: markdown--><pre><code class="language-bash">delv MX ycombinator.com @9.9.9.9
;; none:29: no crypto support
delv: No trusted keys were loaded
</code></pre>
<!--kg-card-end: markdown--><h2 id="some-attempts-to-fix-the-problem">Some Attempts to fix the Problem</h2><p>I did a search on the error message and came across a mailing list <a href="https://comp.protocols.dns.bind.narkive.com/HCDkeGOS/delv-9-12-3-p1-issue-loading-trusted-keys?ref=ayewo.com">discussion</a> from 3 years ago which mentioned the <code>-a anchor-file</code> option from the <a href="https://bind9.readthedocs.io/en/v9_17_10/manpages.html?ref=ayewo.com#options">man page</a>. </p><p>Based on the explanation in that man entry, the default behavior of <code>delv</code> is to look for key information in a default location <code>/etc/bind.keys</code>, if no <code>-a anchor-file</code> option is specified, so I decided to create that file since it was non-existent on my machine.</p><p>Just to be sure, I first check to see what version of <code>delv</code> I have installed:</p><!--kg-card-begin: markdown--><pre><code class="language-bash">delv -v                                                        
delv 9.10.6
</code></pre>
<!--kg-card-end: markdown--><p>So, I use the term &quot;<code>/etc/bind.keys</code>&quot; to search for a valid copy of that file online and landed on the homepage of the <a href="https://www.isc.org/bind-keys/?ref=ayewo.com">ISC</a> (Internet Systems Consortium). It is hosted on their <a href="https://downloads.isc.org/isc/bind9/keys/9.11/bind.keys.v9_11?ref=ayewo.com">FTP site</a> and I was able to download a valid copy (v9.11) with the following commands: </p><!--kg-card-begin: markdown--><pre><code class="language-bash">cd /tmp
curl -LO https://downloads.isc.org/isc/bind9/keys/9.11/bind.keys.v9_11
sudo mv /tmp/bind.keys.v9_11 /etc/bind.keys

</code></pre>
<!--kg-card-end: markdown--><p>With a valid <code>/etc/bind.keys</code> in place, I ran the <code>delv</code> command again and the error message was slightly different:</p><!--kg-card-begin: markdown--><pre><code class="language-bash">delv MX ycombinator.com @9.9.9.9 
;; /private/etc/bind.keys:29: no crypto support
delv: No trusted keys were loaded
</code></pre>
<!--kg-card-end: markdown--><p>Based on additional searches, the real issue is the &quot;no crypto support&quot; part of the error message. This ServerFault <a href="https://serverfault.com/questions/973142/after-updating-bind-from-9-8-to-9-12-it-does-not-start-anymore-with-dnssec-vali?ref=ayewo.com">answer</a> suggests that the Apple-provided version of the <code>delv</code> binary was not set to support SSL when it was compiled.</p><p>Apple&apos;s inclusion of OSS network utilities can sometimes be half-hearted, so rather than play whack-a-mole trying to fix it, I decided to use <em>Homebrew</em>. </p><h2 id="fixing-the-problem">Fixing the Problem</h2><p><code>bind</code> is an <a href="https://gitlab.isc.org/isc-projects/bind9?ref=ayewo.com">OSS</a> collection of <a href="https://www.freshports.org/dns/bind-tools?ref=ayewo.com">network utilities</a> (sometimes called <code>bind-utils</code> or <code>bind-tools</code>) from the <a href="https://www.isc.org/bind/?ref=ayewo.com">ISC</a> that includes CLI tools like <code>dig</code>, <code>host</code>, <code>nslookup</code> and of course <code>delv</code>.</p><p>So, I search for &quot;homebrew bind&quot; and the <a href="https://formulae.brew.sh/formula/bind?ref=ayewo.com">first result</a> gave me a simple way to install it on macOS in only 2 steps.</p><p><strong>Step 1</strong>: Install <code>bind</code> by executing <code>brew install bind</code>:</p><!--kg-card-begin: markdown--><pre><code class="language-bash">brew install bind
...
==&gt; Caveats
==&gt; bind
To restart bind after an upgrade:
  sudo brew services restart bind
Or, if you don&apos;t want/need a background service you can just run:
  /Users/mac/homebrew/opt/bind/sbin/named -f -L /Users/mac/homebrew/var/log/named/named.log
</code></pre>
<!--kg-card-end: markdown--><p><strong>Step 2</strong>: Next, open a new terminal window, and check the version of <code>delv</code> that was installed is newer than the Apple-provided version of <code>9.10.6</code>:</p><!--kg-card-begin: markdown--><pre><code class="language-bash">delv -v
delv 9.18.4
</code></pre>
<!--kg-card-end: markdown--><p>The newer <code>delv</code> binary can be found at this path: <code>/Users/mac/homebrew/Cellar/bind/9.18.4/bin/delv</code> on my Mac and because my <em>Homebrew </em>installation was setup to put <em>Homebrew</em> binaries ahead of Apple-provided binaries in my <code>PATH</code>, the shell found it <em>before</em> it found the older version provided by Apple. </p><p>You can read more about how I set this up towards the end of my article on <a href="https://ayewo.com/switching-to-a-newer-version-of-rsync-on-macos/">using a newer version of rsync on macOS</a>. </p><h3 id="using-the-new-version-of-delv">Using the new version of delv</h3><p>Re-running the original command in the HN comment earlier&#x2013;<code>delv MX ycombinator.com @9.9.9.9</code> &#xA0;now works correctly:</p><!--kg-card-begin: markdown--><pre><code class="language-bash">delv MX ycombinator.com @9.9.9.9 
; unsigned answer
ycombinator.com.	300	IN	MX	10 aspmx.l.google.com.
ycombinator.com.	300	IN	MX	20 alt1.aspmx.l.google.com.
ycombinator.com.	300	IN	MX	20 alt2.aspmx.l.google.com.
ycombinator.com.	300	IN	MX	30 aspmx4.googlemail.com.
</code></pre>
<!--kg-card-end: markdown--><p>The second command in the HN comment&#x2013;<code>delv NS ycombinator.com @9.9.9.9</code> also worked:</p><!--kg-card-begin: markdown--><pre><code class="language-bash">delv NS ycombinator.com @9.9.9.9
; unsigned answer
ycombinator.com.	10267	IN	NS	ns-225.awsdns-28.com.
ycombinator.com.	10267	IN	NS	ns-556.awsdns-05.net.
ycombinator.com.	10267	IN	NS	ns-1411.awsdns-48.org.
ycombinator.com.	10267	IN	NS	ns-1914.awsdns-47.co.uk.

</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Touchpoint Reputation in B2B SaaS]]></title><description><![CDATA[Like humans, customer touchpoints also have a reputation.]]></description><link>https://ayewo.com/the-concept-of-touchpoint-reputation/</link><guid isPermaLink="false">6499621cf941aa2920349d1c</guid><category><![CDATA[Note-to-Self]]></category><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Tue, 16 Aug 2022 21:54:39 GMT</pubDate><content:encoded><![CDATA[<p>Rand Fishkin recently posted the following tweet:</p><figure class="kg-card kg-embed-card kg-card-hascaption"><blockquote class="twitter-tweet" data-width="550"><p lang="en" dir="ltr">Theory: a substantial percent of folks *never* fill out lead forms online. They&apos;ll email, call, send LinkedIn messages, get an intro, but they won&apos;t use your contact form.<br><br>Am I full of crap here? Or do your experiences (maybe even your own behavior) match?</p>&#x2014; Rand Fishkin (@randfish) <a href="https://twitter.com/randfish/status/1559306294162386945?ref_src=twsrc%5Etfw&amp;ref=ayewo.com">August 15, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<figcaption>Source: <a href="https://twitter.com/randfish?ref=ayewo.com">Rand Fishkin</a></figcaption></figure><p>I think the conversation sparked by his thread speaks to the existence of a invisible but important quality of contact forms on B2B websites. </p><p>A contact form, a contact telephone, a contact email etc are merely different ways by which a website visitor can establish initial contact with a company they intend to do business with. Collectively, NNgr0up refers to them as <a href="https://www.nngroup.com/articles/channels-devices-touchpoints/?ref=ayewo.com">touchpoints</a> that are part of a customer&apos;s journey. &#xA0;</p><p>I&apos;m writing this short piece because the Twitter thread generated an interesting thought in my head&#x2013;it seems that touchpoints have a <em>reputation</em> which shapes peoples behavior. <br></p><p>Depending on how a touchpoint&apos;s reputation is perceived, visitors might decide to either use it or seek out an alternative to accomplish the task at hand.</p><p>For instance, if a contact form:</p><ul><li>is asking for too much info (i.e. it will take too much time to complete), or;</li><li>is a bag of uncertainty&#x2013;it offers no hint as to when you&#x2019;ll hear back from the business (i.e. no reassuring note like &quot;we typically respond within 24-48 hrs&quot;);</li></ul><p>a visitor is likely to abort the task at hand or seek out alternative touchpoints known to deliver fast answers.</p><p>Another angle worth considering is past experience&#x2014;if visitors have taken the pains to fill long contact forms and not hear back for weeks, this negative experience will add up and ultimately turn visitors away from bothering with such forms altogether. </p><p>They&apos;d rather seek out alternative touchpoints such as the telephone, email or LinkedIn messages (mentioned in Rand&apos;s tweet) which are known to have a reputation of being manned by an actual human, ensuring that they can get a response faster.</p><p><a href="https://mobile.twitter.com/johnxie/status/1559309547239444480?ref=ayewo.com">John Xie</a> mentioned <a href="https://www.taskade.com/?ref=ayewo.com">Taskade</a>&#x2019;s counterexample of having good contact form usage by site visitors:</p><figure class="kg-card kg-embed-card kg-card-hascaption"><blockquote class="twitter-tweet" data-width="550"><p lang="en" dir="ltr">We&#x2019;ve had plenty of success with our <a href="https://twitter.com/typeform?ref_src=twsrc%5Etfw&amp;ref=ayewo.com">@typeform</a> on <a href="https://twitter.com/Taskade?ref_src=twsrc%5Etfw&amp;ref=ayewo.com">@Taskade</a>&#x2019;s pricing page, I&#x2019;m sure keeping it simple helped!</p>&#x2014; John Xie (@johnxie) <a href="https://twitter.com/johnxie/status/1559309547239444480?ref_src=twsrc%5Etfw&amp;ref=ayewo.com">August 15, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<figcaption>Source: <a href="https://twitter.com/johnxie?ref=ayewo.com">John Xie</a></figcaption></figure><p> I think there are two possible explanations at play here:</p><ul><li>they kept the Typeform-based contact form on their pricing page <em><a href="https://www.taskade.com/request-demo?ref=ayewo.com">simple</a>;</em></li><li>Typeform-based contact forms in general tend to enjoy a good reputation amongst netizens;</li></ul><p>For #1, the contact form has 4 mandatory questions and 1 optional question so the form should only take about a minute complete.</p><p>For #2, businesses that care about providing a smooth form experience often use Typeform over bland competitors, so I&apos;m guessing that when visitors see that a Typeform-like experience is available, they are much more likely to put up with it because it feels <a href="https://twitter.com/dancave/status/1559314202245959681?ref=ayewo.com">different</a>:</p><figure class="kg-card kg-embed-card kg-card-hascaption"><blockquote class="twitter-tweet" data-width="550"><p lang="en" dir="ltr">Type form feels quite different from a traditional lead gen form with the animation and how it behaves. I like em&#x2019;</p>&#x2014; Dan Cave (@dancave) <a href="https://twitter.com/dancave/status/1559314202245959681?ref_src=twsrc%5Etfw&amp;ref=ayewo.com">August 15, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<figcaption>Source: <a href="https://twitter.com/dancave?ref=ayewo.com">Dan Cave</a></figcaption></figure><p>Perhaps there is a direct correlation between businesses that use Typeform-based contact forms and businesses that close the feedback loop quickly when they receive enquiries from website visitors?</p><p>(PS: I hope to someday follow-up this post with a substantive look at the evolution of email as a touchpoint. There was a period when inboxes got overran by unsolicited email, before spam filters got good enough to combat the problem.)</p><h3 id="additional-reading">Additional Reading</h3><p>This article: <a href="https://cfenollosa.com/blog/after-self-hosting-my-email-for-twenty-three-years-i-have-thrown-in-the-towel-the-oligopoly-has-won.html?ref=ayewo.com">After self-hosting my email for twenty-three years I have thrown in the towel</a> via <a href="https://news.ycombinator.com/item?id=32715437&amp;ref=ayewo.com">HN</a> on September 5, 2022 had an interesting discussion that touched on a number of concepts, all related to reputation:</p><ul><li>IP reputation can make or break email deliverability: <a href="https://sendgrid.com/blog/5-ways-check-sending-reputation/?ref=ayewo.com">5 Ways to Check Your Sending Reputation</a>;</li><li><a href="https://news.ycombinator.com/item?id=32718591&amp;ref=ayewo.com">IP range reputation</a> is now being used as a substitute for IP reputation amongst signals for combating spam since (temporary) blockage of an entire IP block (or range) can be effective against spam. This makes sense since if you are on shared infrastructure where there are no anti-spam policies in place, or where neighboring IPs repeatedly get pwned to send out spam, the only way to get your provider&apos;s attention is to drop all traffic originating from their IP range&#x2013;since your provider is unaware or does not care enough to put checks in place.</li></ul>]]></content:encoded></item><item><title><![CDATA[3 Steps to Fix a Failed iCloud Backup on Your iPhone]]></title><description><![CDATA[How to fix the errors: "The last backup could not be completed because of poor network conditions" or "Some files were unavailable during the last backup".]]></description><link>https://ayewo.com/3-steps-to-fix-your-failed-iphone-cloud-backup/</link><guid isPermaLink="false">6499621cf941aa2920349d1a</guid><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Thu, 21 Jul 2022 21:10:39 GMT</pubDate><media:content url="https://ayewo.com/content/images/2022/07/iOS-15.5-iCloud-Backup-Screen-2.png" medium="image"/><content:encoded><![CDATA[<h2 id="synopsis">Synopsis</h2><img src="https://ayewo.com/content/images/2022/07/iOS-15.5-iCloud-Backup-Screen-2.png" alt="3 Steps to Fix a Failed iCloud Backup on Your iPhone"><p>Several weeks ago, I noticed that backups from my iPhone had stopped syncing to iCloud. While trying to fix the issue, I encountered the following error messages on the iCloud Backup screen:</p><ul><li><em>The last backup could not be completed because of poor network conditions.</em></li><li><em>Some files were unavailable during the last backup.</em></li></ul><p>My situation was a bit unique because I use a second iPhone as a WiFi hotspot, so it took me some time to resolve the issue.</p><h2 id="background">Background</h2><p>This is a story about two iPhones: the first is running iOS 15.5 and the second is running iOS 12.5.5. The second iPhone is stuck on an old iOS version due to the device being a fairly old but still functional iPhone 6, which I bought new back in 2015.</p><p>In spite of several years of constant use, the second iPhone has refused to die, so I decided to use it as a Personal Hotspot&#x2014;to provide (nearly) unmetered Internet access to the first iPhone over WiFi.</p><p>And for over 7 months, it was quite reliable as a WiFi hotspot before I noticed in late June that iCloud Backups had started failing on the first iPhone. It had stopped syncing my backups to iCloud for about two weeks.</p><h2 id="my-theory-as-to-the-culprit">My Theory as to the Culprit</h2><p>The second iPhone no longer receives updates&#x2014;it has been stuck on iOS 12.5.5 since September 2021 (and started duty as a WiFi hotspot in early December 2021), which rules it out as the culprit.</p><p>My suspicions turned to the first iPhone because I remember updating it to iOS 15.5 in early June, around the time of WWDC 2022 (6 Jun &#x2013; 10 Jun 2022). The iCloud Backup screen showed June 10 as the last backup date, which lines up nicely with my recollection of when I updated iOS.</p><h2 id="fixing-the-icloud-backup-error">Fixing the iCloud Backup Error </h2><p>I scanned the <a href="https://support.apple.com/en-us/HT213258?ref=ayewo.com">security content</a> as well as the <a href="https://developer.apple.com/documentation/ios-ipados-release-notes/ios-ipados-15_5-release-notes?ref=ayewo.com">release notes</a> for iOS 15.5 to see if there were any changes to the Personal Hotspot feature or something less visible like an update to the networking stack, but I didn&#x2019;t find anything to latch on, so I knew I had to copy-paste the iCloud Backup error message into Google Search to make any progress.</p><h3 id="step-1">Step 1</h3><p>The first error I was shown was: &#x201C;<em><a href="https://www.google.com/search?q=The+last+backup+could+not+be+completed+because+of+poor+network+conditions&amp;ref=ayewo.com">The last backup could not be completed because of poor network conditions</a></em>&#x201D;, so I <strong>switched to a WiFi connection</strong> and restarted the backup process. This worked because I was shown a different error after the restarted backup failed.</p><p>So, <strong>step 1</strong> is to <strong>change from Personal Hotspot to a &apos;proper&apos; WiFi connection.</strong></p><h3 id="steps-2-3">Steps 2 &amp; 3</h3><p>The new error read: &#x201C;<em><a href="https://www.google.com/search?q=Some+files+were+unavailable+during+the+last+backup&amp;ref=ayewo.com">Some files were unavailable during the last backup</a></em>&#x201D; and after skimming several articles, I tried out the following suggestions:</p><ul><li><a href="https://support.apple.com/en-us/HT201559?ref=ayewo.com"><strong>restarting the device</strong></a> &#x2013; <strong>step 2</strong>;</li><li><a href="https://support.apple.com/en-us/HT201330?ref=ayewo.com"><strong>closing several apps</strong></a> &#x2013; <strong>step 3</strong>;</li><li>signing-out &amp; signing-back-in with your Apple ID &#x2013; (optional) step 4.</li></ul><p>So, I did everything from <strong>rebooting the iPhone </strong>(multiple times), <strong>close several apps </strong>(almost 2 dozen of them)<strong>,</strong> to attempting to sign-out and sign-back-in with my Apple ID. iOS showed a scary warning about deleting files stored on the device if I were to proceed with signing out so I aborted this suggestion.</p><p>I tried again, and after about an hour or so, the backup completed successfully &#x1F4AF;!</p><h2 id="no-official-fix-yet">No Official Fix Yet</h2><p>Today, I put the second iPhone in Personal Hotspot mode so I could use it to access the Internet on the first iPhone. I attempted an iCloud Backup and I received the same error as I got the first time: &quot;<em>The last backup could not be completed because of poor network conditions&quot;,</em> which makes the error reproducible. </p><p>I have updated to <a href="https://support.apple.com/en-us/HT213346?ref=ayewo.com">iOS 15.6</a> which was released yesterday (July 20) and the error remains.</p><h2 id="epilogue-the-root-cause-">Epilogue: the Root Cause&#x200C;</h2><p>iCloud Backups via another iPhone in Personal Hotspot mode used to work fine prior to iOS 15.5. It seems that Apple may have introduced a silent, or more correctly, undocumented, change in May 2022 when iOS 15.5 was released. </p><p>An iPhone running iOS 15.5 now treats a WiFi connection from another iPhone in Personal Hotspot mode differently from a regular WiFi connection, which explains why my iCloud Backups started failing after I updated from iOS 15.4 to 15.5.</p><p>I&apos;m fairly certain because the first step in the instructions above, where I switched from using the second iPhone in Personal Hotspot mode to a &quot;proper&quot; WiFi connection, was really just me popping out the LTE SIM from the iPhone and plopping it into another phone also in hotspot mode, but running a different OS (Android instead of iOS). </p>]]></content:encoded></item><item><title><![CDATA[Exploding Career Opportunities in DevRel]]></title><description><![CDATA[Looking to land a job in developer relations aka devrel?]]></description><link>https://ayewo.com/exploding-career-opportunities-in-devrel/</link><guid isPermaLink="false">6499621cf941aa2920349d19</guid><category><![CDATA[article]]></category><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Sat, 22 Jan 2022 22:09:13 GMT</pubDate><media:content url="https://ayewo.com/content/images/2022/01/Roles-in-DevRel-Airtable.png" medium="image"/><content:encoded><![CDATA[<img src="https://ayewo.com/content/images/2022/01/Roles-in-DevRel-Airtable.png" alt="Exploding Career Opportunities in DevRel"><p>I&apos;ve been tracking job openings in DevRel (Developer Relations) and adjacent roles in a private document in Notion for a while now. I&apos;ve been using these job ads as a proxy&#x2013;to help me discover companies that I wasn&apos;t aware of as a maker of tools for developers. </p><h2 id="devscape">DevScape</h2><p>Why? Back in July 2021, I bought a domain&#x2013;devscape.co&#x2013;where I plan to write in-depth about the developer landscape:</p><figure class="kg-card kg-embed-card kg-card-hascaption"><blockquote class="twitter-tweet" data-width="550"><p lang="en" dir="ltr">I bought a domain in July where I plan to share fascinating shifts that are steadily changing the Developer Landscape.<br><br>It&apos;s eventual home: <a href="https://t.co/YlAhBIiS9O?ref=ayewo.com">https://t.co/YlAhBIiS9O</a><br><br>I hope to get to it soon, but I have a handful of side projects that I need to ship/polish a bit more.</p>&#x2014; ayewo (@ayewo_) <a href="https://twitter.com/ayewo_/status/1433196381808779266?ref_src=twsrc%5Etfw&amp;ref=ayewo.com">September 1, 2021</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<figcaption>Tweet where I announce https://devscape.co</figcaption></figure><p>DevScape would also make for a great home for several of the things that I have been working on on the side, including a Playbook for DevRel practioners, growth strategy tear downs and potentially&#x2013;a job board. If the experiment works out, DevScape could make money from subscriptions because the developer landscape is a <em>fast</em> growing <a href="https://twitter.com/KarlLHughes/status/1482010630773260299?ref=ayewo.com">niche</a>. </p><h2 id="dev-rel-employers-often-lead-to-dev-tool-makers">Dev Rel Employers Often Lead to Dev Tool Makers</h2><p>Using devrel job ads as a proxy for locating dev tool companies strewn across the dev tool landscape, I have been recording new finds in a free-form document in Notion but have come to realize that I&apos;m slowly setting myself for a world of hurt later. </p><p>Going over my notes helped me realize that there are so many such companies that it makes more sense to use a tabular structure versus a free-form structure. A table is easier to scan, sort and reorganize than free-form copy-pasta. So, that&apos;s exactly what I did yesterday and today: move the data from Notion to Airtable. </p><h2 id="dev-rel-job-titles">Dev Rel Job Titles</h2><p>The most common job title for the role of a developer that markets other developers is <em>developer advocate</em>, but quite a few companies opt for a different title. </p><p>Some examples of such titles include <em>developer relations engineer, open source evangelist, community manager, developer relations leader </em>etc, perhaps to emphasize how each role will require specific skills like professional coding experience, high visibility in the OSS (Open Source Software) community, past experience managing a community, or past experience managing a team, respectively.</p><p>A sampling of the titles I currently track include: </p><ul><li>Developer Advocate</li><li>Developer Educator</li><li>Developer Relations Engineer</li><li>Developer Evangelist</li><li>Open Source Evangelist</li><li>Technical Evangelist</li><li>Data Advocate</li><li>Data Evangelist</li><li>Developer Relations Manager</li><li>Developer Advocacy Leader</li></ul><h2 id="airtable">Airtable</h2><p>Today, I&apos;d like to share my Airtable publicly, in the hope that it will be useful to veterans looking for a new job and newbies looking to make the jump to devrel.</p><p>Without further ado, the Airtable is embedded below:</p><!--kg-card-begin: html--><iframe class="airtable-embed" src="https://airtable.com/embed/shrQmT9UVk6tKxFhE?backgroundColor=blue&amp;viewControls=on" frameborder="0" onmousewheel width="100%" height="533" style="background: transparent; border: 1px solid #ccc;"></iframe><!--kg-card-end: html--><p></p><p>I plan to regularly update the table, so be sure to bookmark this page.</p><p>(PS: The curator of DevRel Weekly: <a href="https://www.marythengvall.com/?ref=ayewo.com">Mary Thengvall</a> also maintains a <a href="https://raindrop.io/mary27/dev-rel-weekly-jobs-10525990/?ref=ayewo.com">public bookmark</a> of devrel roles that is worth checking out.)</p>]]></content:encoded></item><item><title><![CDATA[Engineering vs. Technology]]></title><description><![CDATA[<p>In this article, I attempt to chronicle how I arrived at what I think is a layman explanation of the difference between the terms engineering and technology .</p><h2 id="an-incomplete-devil-s-dictionary">An Incomplete Devil&#x2019;s Dictionary</h2><p>My goal with this series is to offer layman definitions of words used in the technology industry.</p>]]></description><link>https://ayewo.com/engineering-vs-technology-company/</link><guid isPermaLink="false">6499621cf941aa2920349d0b</guid><category><![CDATA[article]]></category><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Thu, 02 Dec 2021 01:16:38 GMT</pubDate><content:encoded><![CDATA[<p>In this article, I attempt to chronicle how I arrived at what I think is a layman explanation of the difference between the terms engineering and technology .</p><h2 id="an-incomplete-devil-s-dictionary">An Incomplete Devil&#x2019;s Dictionary</h2><p>My goal with this series is to offer layman definitions of words used in the technology industry. I try to be clear and concise, in the style of the concise (and witty) definitions that appear in <a href="https://en.wikipedia.org/wiki/The_Devil%27s_Dictionary?utm_campaign=Weekly%20newsletter%20of%20Ayewo&amp;utm_medium=email&amp;utm_source=Revue%20newsletter">The Devil&#x2019;s Dictionary</a>. The Devil&apos;s Dictionary was first published in 1911 by <a href="https://en.wikipedia.org/wiki/Ambrose_Bierce?utm_campaign=Weekly%20newsletter%20of%20Ayewo&amp;utm_medium=email&amp;utm_source=Revue%20newsletter">Ambrose Bierce</a>.</p><h2 id="a-long-primer">A Long Primer</h2><p>Two years ago, I read and thoroughly enjoyed Herbert Simon&apos;s <em>The Sciences of the Artificial (SotA). </em>In terms of information density,<em> SotA</em> is a phenomenal book&#x2013;it is almost as dense as another personal favorite of mine: <em>The Structure of Scientific Revolutions (SoSR) </em>by Thomas Kuhn<em>.</em> </p><p>Both books were first published in the 1960s&#x2013;<em>SoSR </em>was first published in 1962, while <em>SotA </em>was first published in 1969. I read the 1970 edition (aka 2nd edition) of <em>SoSR, </em>while I read the 1996 edition (aka 3rd edition) of <em>SotA.</em></p><p>Why did I choose to read books from 50-60 years ago? </p><h3 id="a-getaway-for-looming-questions">A Getaway for Looming Questions</h3><p>I forget where I first saw the recommendations to read <em>SoSR,</em> but I suspect it must have been on HackerNews. </p><p>Every once in a while, someone would leave a <a href="https://news.ycombinator.com/item?id=9364301&amp;ref=ayewo.com">comment</a> pointing out the human tendency to make assumptions about the history of science, rather than <a href="https://news.ycombinator.com/item?id=10288175&amp;ref=ayewo.com">consult</a> the actual history of science. Recommending <em>SoSR </em>was often the antidote to that malaise. The nudge to read it often<em> </em>increased when the concept of <em>paradigm shifts </em>which was made famous by the book came up for <a href="https://news.ycombinator.com/item?id=16242908&amp;ref=ayewo.com">discussion</a>. I finally read it in 2018.</p><p>Reading <em>SoSR </em>helped answer several open questions that had been floating in my head for years, and more importantly, it created a new set of open questions deserving of their own answers.</p><h3 id="journeying-with-a-clear-thinker">Journeying With a Clear Thinker</h3><p><em>SoSR</em> absolutely showed how clear thinking can elevate your writing&#x2013;reading it set the bar pretty high for what I now consider a good book. Today, it&apos;s hard to find books that can match the information density of <em>SoSR, </em>but it didn&apos;t stop me from being on the look out for other books of similar calibre.</p><h3 id="journeying-with-another-clear-thinker">Journeying With Another Clear Thinker</h3><p>I came across the recommendation to read <em>SotA</em> in this strong nod to Herbert Simon as a thinker by Alan Kay on <a href="https://www.quora.com/Experienced-programmers-and-computer-scientists-what-are-some-really-old-or-even-nearly-forgotten-books-you-think-every-new-programmer-should-read/answer/Alan-Kay-11?ref=ayewo.com">Quora</a>:</p><blockquote>Try &#x201C;The Sciences of the Artificial&#x201D; by Herb Simon. A much stronger way to think about computing &#x2014; and what &#x201C;Computer Science&#x201D; might mean &#x2014; <strong>by a much stronger thinker than most today</strong>.</blockquote><p>I read <em>SotA </em>in 2019 and agree with Alan Kay&#x2013;Herbert Simon was indeed a strong thinker though I prefer the term clear thinker. </p><p>Granted, many readers will consider them old books because of the time they were written, nonetheless, I think reading them and taking copious notes will sharpen one&apos;s thinking. I strongly recommend both books to anyone that wants to learn how to think from first principles, rather than by analogy. </p><h2 id="a-small-but-asute-observation">A Small but Asute Observation</h2><p>For several years now, my daily routine often involves doing a ton of reading online about the technology industry. Topics are wide and varied, but reading <em>SotA </em>forced me to reflect on a subtle but important observation. </p><h3 id="conflating-engineering-with-technology">Conflating Engineering with Technology</h3><p>A lot of writers, especially online, use the words <em>engineering</em> and <em>technology</em> interchangeably even though they are not synonyms. This contrasts sharply with how Herbert Simon used them<em> </em>in his book. </p><p>In <em>SotA, </em>science, engineering and technology roughly approximate to the following distinct concepts: analysis, synthesis and tooling, respectively.</p><h3 id="an-initial-but-incorrect-diagnosis">An Initial But Incorrect Diagnosis</h3><p>It turns out that my initial diagnosis was wrong. Or more accurately, the premise was correct but the conclusion was wrong.</p><p>Yes, writers are increasingly using the words engineering and technology interchangeably, <em>but not as synonyms</em>.</p><h3 id="resolving-the-misdiagnosis">Resolving the Misdiagnosis </h3><p>To illustrate the issue, lets attempt to answer the following question which should have a fairly simple answer: what <em>kind</em> of company is Google? Is Google an engineering company or a technology company?</p><p>Google <a href="https://careers.google.com/teams/engineering-technology/?ref=ayewo.com">describes</a> (<a href="https://web.archive.org/web/20170111231047/https://www.google.com/intl/ka/about/careers/teams/engineering/">1</a>, <a href="https://web.archive.org/web/20211102121558/https://careers.google.com/teams/engineering-technology/">2</a>) itself as an <em>engineering</em> company on the Engineering &amp; Design section of its careers site:</p><blockquote>Google is and always will be an engineering company. We hire people with a broad set of technical skills who are ready to tackle some of technology&#x2019;s greatest challenges and make an impact on millions, if not billions, of users.</blockquote><p>Wikipedia <a href="https://en.wikipedia.org/w/index.php?title=Google&amp;utm_campaign=Weekly%20newsletter%20of%20Ayewo&amp;utm_medium=email&amp;utm_source=Revue%20newsletter">describes</a> Google as a <em>technology</em> company:</p><blockquote>Google LLC is an American multinational technology company that specializes in Internet-related services and products, which include online advertising technologies, a search engine, cloud computing, software, and hardware. </blockquote><p>While Google says it is an engineering company, Wikipedia disagrees, describing them as a technology company instead. Which characterization is more accurate, the one by Google or Wikipedia?</p><p>To answer this follow-on question, we need to agree on a layman definition for each word in order to understand how the underlying activities differ. In other words, what is the defining trait that allows us to tell apart engineering companies from their technology counterparts?</p><h3 id="verbose-definitions">Verbose Definitions</h3><p>The individual definitions of <a href="https://en.wikipedia.org/wiki/Engineering?ref=ayewo.com">engineering</a> and <a href="https://en.wikipedia.org/wiki/Technology?ref=ayewo.com">technology</a> on Wikipedia are too verbose to be useful here for two reasons:</p><ul><li>their definitions partially depend on a definition for <a href="https://en.wikipedia.org/wiki/Science?ref=ayewo.com">science</a>;</li><li>the definition of science is too academic to expressed in a concise way.</li></ul><p>I remember repeatedly pausing my reading to search for a modern layperson explanation of how the two concepts differ, but none of the definitions I came across were intuitive or concise.</p><h3 id="concise-definitions">Concise Definitions</h3><p>So I took a stab at it and decided to maintain a layperson list of word definitions on my device. By the time I had finished reading <em>SotA</em>, I had come up with a fairly stable definition for the terms engineering and technology. For completeness, I added concise definitions for science and mathematics, which altogether make up the <a href="https://en.wikipedia.org/wiki/Science,_technology,_engineering,_and_mathematics?ref=ayewo.com">STEM</a> acronym.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://ayewo.com/stem/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">STEM: Science, Technology, Engineering, and Mathematics</div><div class="kg-bookmark-description">A clear and concise definition for each discipline that makes up the STEM acronym.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://ayewo.com/favicon.png" alt><span class="kg-bookmark-author">Misleading Metaphors</span><span class="kg-bookmark-publisher">Sa&#xEF;d</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://ayewo.com/content/images/size/w100/2020/12/favicon-1.png" alt></div></a></figure><h3 id="resolving-the-question">Resolving the Question</h3><p>Armed with the following characterizations:</p><ul><li>engineering is preoccupied with <em>working within constraints;</em></li><li>technology is preoccupied with <em>transcending constraints</em> (with tools);</li></ul><p>let&apos;s attempt to answer our last question: &quot;which characterization is more accurate, the one by Google or Wikipedia?&quot;</p><p>I&apos;d say that Google is engaged in both kinds of activities as is evidenced by the copy on the Consumer Hardware section of their careers site:</p><blockquote>Our Consumer Hardware team researches, designs, and <strong>develops new technologies</strong> and hardware to make users&#x2019; interaction with computing faster, more powerful, and seamless. Whether finding ways to capture and sense the world around us, advancing form factors, or improving interaction methods, our Consumer Hardware team <strong>is making people&#x2019;s lives better through technology</strong>.</blockquote><p>Google is a multi-faceted entity which is why it can be referred to as an engineering company, a technology company or a <a href="https://www.npr.org/2020/10/20/925736276/google-abuses-its-monopoly-power-over-search-justice-department-says-in-lawsuit?ref=ayewo.com">monopoly</a>. Whether a label is appropriate is context-dependent.</p><p>Essentially, the real-world is complex. Calling Google an engineering company or a technology company is a matter of perspective. Both are correct. </p><p></p><p></p><p></p><p></p><p>&#x2003;</p>]]></content:encoded></item><item><title><![CDATA[STEM: Science, Technology, Engineering, and Mathematics]]></title><description><![CDATA[A clear and concise definition for each discipline that makes up the STEM acronym.]]></description><link>https://ayewo.com/stem/</link><guid isPermaLink="false">6499621cf941aa2920349d18</guid><category><![CDATA[article]]></category><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Wed, 01 Dec 2021 15:56:38 GMT</pubDate><content:encoded><![CDATA[<h2 id="motivation">Motivation</h2><p>I have yet to come across a satisfactory definition for any of the individual words that make up the acronym STEM: science, technology, engineering, and mathematics, so I&apos;d like to offer my own.</p><h3 id="science">Science</h3><p>Science is preoccupied with the <em>discovery of</em> <em>constraints</em>.</p><h3 id="engineering">Engineering</h3><p>Engineering is preoccupied with <em>working within</em> <em>constraints</em>.</p><h3 id="technology">Technology</h3><p>Technology is preoccupied with <em>transcending constraints</em> (with tools).</p><h3 id="mathematics">Mathematics</h3><p>Mathematics is preoccupied with <em>expressing</em> <em>constraints</em> (with symbols).</p><h2 id="summary">Summary</h2><p>All four (4) definitions highlight an important theme that exists across STEM fields: the concept of <em>constraints</em>. </p><p>Each definition re-frames, in a handful of words, the four STEM fields as variations on one underlying human endeavor&#x2014;the human need to <em>deal with</em> <em>constraints. </em></p><p>A side-effect of this re-framing using the concept of <em>constraints </em>is that the definitions are fairly easy to recall&#x2013;at least for me.</p>]]></content:encoded></item><item><title><![CDATA[rsync on macOS: Matters Arising]]></title><description><![CDATA[Setting up your macOS to use rsync v3.2.3 from Homebrew, the right way.]]></description><link>https://ayewo.com/switching-to-a-newer-version-of-rsync-on-macos/</link><guid isPermaLink="false">6499621cf941aa2920349d17</guid><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Sun, 21 Nov 2021 11:27:26 GMT</pubDate><media:content url="https://ayewo.com/content/images/2021/11/rsync-macos-catalina.png" medium="image"/><content:encoded><![CDATA[<img src="https://ayewo.com/content/images/2021/11/rsync-macos-catalina.png" alt="rsync on macOS: Matters Arising"><p>I recently had to transfer large files on Windows to macOS Catalina (10.15.7) using <em>rsync</em>, but I met a few snags on the way. I already wrote about my experience using <a href="https://ayewo.com/how-to-install-rsync-on-windows/">rsync for the first time on Windows</a>. </p><p>This article is the concluding part that delves into the issues I encountered on the macOS side of the transfer.</p><h2 id="too-many-bytes">Too many bytes?</h2><p>The transfer went smoothly for the most part, until <em>rsync</em> got to an ~18GB <em>.vdi </em>file used by one of my VirtualBox VMs:</p><!--kg-card-begin: markdown--><pre><code>bash -c &quot;rsync -avzh -P --remove-source-files --stats --timeout=60  VirtualBox &apos;mac@10.0.0.104:/Users/mac/Windows8-Lenovo/&apos;&quot;
(mac@10.0.0.104) Password:
building file list ...
65 files to consider
VirtualBox/Ubuntu 18.04 LTS + Upwork/Ubuntu 18.04 LTS.vdi
          9.63G  54%   68.12MB/s    0:01:55
rsync: [sender] write error: Broken pipe (32)
rsync error: timeout in data send/receive (code 30) at /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/rsync/rsync-54.120.1/rsync/io.c(164) [receiver=2.6.9]
rsync: connection unexpectedly closed (1747 bytes received so far) [generator]
rsync error: error in rsync protocol data stream (code 12) at /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/rsync/rsync-54.120.1/rsync/io.c(453) [generator=2.6.9]
rsync error: error in rsync protocol data stream (code 12) at io.c(823) [sender=3.2.3]
</code></pre>
<!--kg-card-end: markdown--><p>I scanned the error message and figured it might have been due to a minor network issue, so I re-attempted the transfer two more times and the error was fairly reproducible.</p><p>Here&apos;s the output from the 2nd try:</p><!--kg-card-begin: markdown--><pre><code>65 files to consider
VirtualBox/Ubuntu 18.04 LTS + Upwork/Ubuntu 18.04 LTS.vdi
          9.66G  54%   10.34MB/s    0:12:37
rsync: [sender] write error: Broken pipe (32)
rsync error: timeout in data send/receive (code 30) at /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/rsync/rsync-54.120.1/rsync/io.c(164) [receiver=2.6.9]
rsync: connection unexpectedly closed (1747 bytes received so far) [generator]
</code></pre>
<!--kg-card-end: markdown--><p>Here&apos;s the output from the 3rd try:</p><!--kg-card-begin: markdown--><pre><code>65 files to consider
VirtualBox/Ubuntu 18.04 LTS + Upwork/Ubuntu 18.04 LTS.vdi
          9.62G  54%   68.44MB/s    0:01:54
rsync: [sender] write error: Broken pipe (32)
rsync error: timeout in data send/receive (code 30) at /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/rsync/rsync-54.120.1/rsync/io.c(164) [receiver=2.6.9]
rsync: connection unexpectedly closed (1747 bytes received so far) [generator]
</code></pre>
<!--kg-card-end: markdown--><p>So, after 3 attempts, the transfer fails at exactly 54% of the way during the transfer of an 18GB file. </p><h2 id="filesystem-limitation">Filesystem limitation?</h2><p>I&apos;ve experienced a similar kind of error a long time ago while transferring a large file to FAT-based filesystem used by Windows, so my first thought was that this was also a filesystem limitation issue. </p><p>The difference here was that Windows was the source and not the destination of the large file transfer, so I decided to search for the first error message in the output <em>&quot;rsync: [sender] write error: Broken pipe (32)&quot;.</em></p><h2 id="unable-to-rsync-due-to-broken-pipe">Unable to rsync due to broken pipe</h2><p>The first result on Google: <a href="https://serverfault.com/questions/883487/unable-to-rsync-due-to-broken-pipe?ref=ayewo.com">Unable to rsync due to broken pipe</a> turned out to be especially useful in pointing me towards the underlying issue&#x2013;it turned out I was using two different versions of <em>rsync. </em></p><p>Just to be sure, I went ahead to re-read the error output much more closely:</p><!--kg-card-begin: markdown--><blockquote>
<p>rsync: [sender] write error: Broken pipe (32)</p>
<p>rsync error: timeout in data send/receive (code 30) at /AppleInternal\</p>
<p>/BuildRoot/Library/Caches/com.apple.xbs/Sources/rsync/\</p>
<p>rsync-54.120.1/rsync/io.c(164) <strong>[receiver=2.6.9]</strong></p>
</blockquote>
<blockquote>
<p>rsync: connection unexpectedly closed (1747 bytes received so far) [generator]</p>
<p>rsync error: error in rsync protocol data stream (code 12) at \</p>
<p>/AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/\</p>
<p>Sources/rsync/rsync-54.120.1/rsync/io.c(453) [generator=2.6.9]</p>
<p>rsync error: error in rsync protocol data\</p>
<p>stream (code 12) at io.c(823) <strong>[sender=3.2.3]</strong></p>
</blockquote>
<!--kg-card-end: markdown--><p>On the sending machine (Windows) I had rsync v3.2.3, while on the receiving machine (macOS) I had rsync v2.6.9 installed:</p><!--kg-card-begin: markdown--><pre><code>rsync --version
rsync  version 2.6.9  protocol version 29
Copyright (C) 1996-2006 by Andrew Tridgell, Wayne Davison, and others.
&lt;http://rsync.samba.org/&gt;
Capabilities: 64-bit files, socketpairs, hard links, symlinks, batchfiles,
              inplace, IPv6, 64-bit system inums, 64-bit internal inums

rsync comes with ABSOLUTELY NO WARRANTY.  This is free software, and you
are welcome to redistribute it under certain conditions.  See the GNU
General Public Licence for details.
</code></pre>
<!--kg-card-end: markdown--><h2 id="fixing-the-problem">Fixing the problem</h2><p>The fix was fairly easy&#x2013;I had to update the dated v2.6.9 copy of <em>rsync</em> on macOS to match v3.2.3 on Windows.</p><pre><code>brew install rsync</code></pre><p>There was a slight problem though, because of how my <em>Homebrew</em> installation was setup to put all binaries under <a href="https://stackoverflow.com/a/35867178?ref=ayewo.com"><em>~/homebrew/bin</em></a><em>.</em> <br></p><p>Version 2.6.9 of <em>rsync</em> at <em>/usr/bin/rsync</em> was getting found first in my PATH instead of v3.2.3 that I had just installed to <em>~/homebrew/bin/rsync</em>. </p><h2 id="fixing-a-latent-problem">Fixing a latent problem</h2><p>I had been putting off the <em>Homebrew</em> PATH issue for a while due to my unfamiliarity with the default <em>zsh </em>on macOS, but this time, I decided I was going to properly research how <em>zsh</em> differs from the more familiar <em>bash, </em>so I could fix the PATH issue the correct way, rather than cargo-cult magical incantations from the Internet. </p><p>I&apos;ve distilled my research into two comment lines just before the actual fix for <em>zsh</em>:</p><pre><code># In https://apple.stackexchange.com/a/425894 it mentions /etc/zprofile (different from my pre-existing ~/.zshenv and ~/.zshrc)

# In /etc/zprofile it says to add customizations to ~/.zprofile instead, so this is where our Homebrew&apos;s rsync v3.2.3 can overshadow Apple&apos;s v2.6.9 in the PATH

echo &quot;export PATH=/Users/mac/homebrew/bin:$PATH&quot; &gt; ~/.zprofile</code></pre><p>Running <code>rsync --version</code> from anywhere now gives the newer version:</p><!--kg-card-begin: markdown--><pre><code>rsync  version 3.2.3  protocol version 31
Copyright (C) 1996-2020 by Andrew Tridgell, Wayne Davison, and others.
Web site: https://rsync.samba.org/
Capabilities:
    64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints,
    socketpairs, hardlinks, hardlink-specials, symlinks, IPv6, no atimes,
    batchfiles, inplace, append, ACLs, xattrs, optional protect-args, iconv,
    symtimes, no prealloc, stop-at, crtimes, file-flags
Optimizations:
    SIMD, asm, openssl-crypto
Checksum list:
    xxh128 xxh3 xxh64 (xxhash) md5 md4 none
Compress list:
    zstd lz4 zlibx zlib none

rsync comes with ABSOLUTELY NO WARRANTY.  This is free software, and you
are welcome to redistribute it under certain conditions.  See the GNU
General Public Licence for details.
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[7 Step Guide to Installing rsync on Windows]]></title><description><![CDATA[<p>I recently needed to move several gigabytes of data from an old laptop running Windows 8.1 to another laptop running macOS and my options were to use an external disk or a network transfer. I wanted the whole process to be unattended so I naturally gravitated towards doing the</p>]]></description><link>https://ayewo.com/how-to-install-rsync-on-windows/</link><guid isPermaLink="false">6499621cf941aa2920349d16</guid><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Wed, 13 Oct 2021 13:04:17 GMT</pubDate><media:content url="https://ayewo.com/content/images/2021/10/rsync.exe.png" medium="image"/><content:encoded><![CDATA[<img src="https://ayewo.com/content/images/2021/10/rsync.exe.png" alt="7 Step Guide to Installing rsync on Windows"><p>I recently needed to move several gigabytes of data from an old laptop running Windows 8.1 to another laptop running macOS and my options were to use an external disk or a network transfer. I wanted the whole process to be unattended so I naturally gravitated towards doing the transfer over my WiFi network.</p><p>For the network transfer, the easiest method I could think of was to use <code>scp</code> but it turns out that there&apos;s no built-in support to &quot;move&quot; files with <code>scp</code>. My options were to write a script to delete each file after getting copied or use a different tool with file move semantics.</p><p>A couple of Google searches later, the most <a href="https://serverfault.com/questions/363922/how-to-move-files-with-scp?ref=ayewo.com">up-voted tool on Serverfault</a> for the task is <code>rsync</code>. I&apos;ved used <code>rsync</code> before but I have never used it on a Windows box so this was an interesting option to try out.</p><p>Some additional searches led me to this answer <a href="https://superuser.com/questions/300263/how-to-use-rsync-from-windows-pc-to-remote-linux-server?ref=ayewo.com">how to use rsync from Windows PC to remote Linux server?</a> that I was hoping would be a drop-in solution to my situation.</p><p>Unfortunately, I couldn&apos;t get <code>rsync</code> working on my first try because the instructions in the most-upvoted <a href="https://superuser.com/a/915733?ref=ayewo.com">answer</a> along with other answers on the question no longer work in 2021. Even this somewhat useful article on <a href="https://tlundberg.com/blog/2020-06-15/installing-rsync-on-windows/?ref=ayewo.com">installing rsync on Windows</a> from more than a year ago, which I found on Google, was almost completely out of date.</p><p>I had to figure out a few missing steps to get <code>rsync</code> working on Windows, so I decided to publish step-by-step instructions to help my future self and others in a similar situation. I tested the commands below on Windows 8.1, nonetheless they should work on Windows 10 or 11.</p><p>The macOS side of things are fairly straight forward so I wont go into that here. </p><h2 id="instructions">Instructions</h2><ol><li>Install <a href="https://gitforwindows.org/?ref=ayewo.com">Git for Windows</a> to the path &quot;C:\Program Files\Git&quot;.<br>In my case I already had it installed so installation merely updated my version to v2.33.0.2.</li></ol><p>2. Visit <a href="https://repo.msys2.org/msys/x86_64/?ref=ayewo.com">https://repo.msys2.org/msys/x86_64/</a> and download the following three (3) file archives:</p><ul><li><a href="https://repo.msys2.org/msys/x86_64/rsync-3.2.3-1-x86_64.pkg.tar.zst?ref=ayewo.com">rsync-3.2.3-1-x86_64.pkg.tar.zst</a> - the <code>rsync</code> binary for Windows;</li><li><a href="https://repo.msys2.org/msys/x86_64/libxxhash-0.8.0-1-x86_64.pkg.tar.zst?ref=ayewo.com">libxxhash-0.8.0-1-x86_64.pkg.tar.zst</a> - a (cryptographic) dll that <code>rsync</code> depends on;</li><li><a href="https://repo.msys2.org/msys/x86_64/libzstd-1.5.0-1-x86_64.pkg.tar.zst?ref=ayewo.com">libzstd-1.5.0-1-x86_64.pkg.tar.zst</a> - a (compression) dll that <code>rsync</code> depends on.</li></ul><p>3. The <code>.zst</code> extension indicates that the files were compressed using the fast Zstandard compression algorithm open sourced by <a href="https://github.com/facebook/zstd?ref=ayewo.com">Facebook</a> so I had to download another (open source) tool to help me extract those files locally: <a href="https://github.com/peazip/PeaZip/releases/download/8.2.0/peazip-8.2.0.WIN64.exe?ref=ayewo.com">PeaZip for Windows</a> v8.2.0.</p><p>4. Create a temporary folder called &quot;C:\tmp&quot; then use PeaZip to extract the 3 files we downloaded earlier into this folder, in two (2) steps.</p><p>The 1st step is to use PeaZip to convert <code>.tar.zst</code> files to <code>.tar</code> files:</p><ul><li><code>rsync-3.2.3-1-x86_64.pkg.tar.zst</code> -&gt; <code>rsync-3.2.3-1-x86_64.pkg.tar</code></li><li><code>libxxhash-0.8.0-1-x86_64.pkg.tar.zst</code> -&gt; <code>libxxhash-0.8.0-1-x86_64.pkg.tar</code></li><li><code>libzstd-1.5.0-1-x86_64.pkg.tar.zst</code> -&gt; <code>libzstd-1.5.0-1-x86_64.pkg.tar</code></li></ul><p>The 2nd step is to use PeaZip to extract the contents of each <code>.tar</code> file. Note that all 3 archives contain a folder named <code>\usr</code> with different contents, so be sure to click &quot;yes&quot; when prompted to overwrite the contents of the <code>C:\tmp\usr</code> folder during the extraction process.</p><p>5. Now, you need to move the <code>C:\tmp\usr</code> folder to its final destination i.e. <code>C:\Program Files\Git</code>, or the location where you have Git for Windows installed.</p><p>Essentially, we need to merge the contents of <code>C:\tmp\usr</code> with <code>C:\Program Files\Git\usr</code> so that the <code>rsync</code> binary (and its dependencies) will end up at the following path <code>C:\Program Files\Git\usr\bin\rsync.exe</code>.</p><p>6. The final step is to confirm that you have a working installation of <code>rsync</code> from a command prompt by executing these 2 commands:</p><p><code>cd &quot;C:\Program Files\Git\usr\bin&quot;</code> and <code>rsync --version</code></p><!--kg-card-begin: markdown--><pre><code>rsync.exe --version
rsync  version 3.2.3  protocol version 31
Copyright (C) 1996-2020 by Andrew Tridgell, Wayne Davison, and others.
Web site: https://rsync.samba.org/
Capabilities:
    64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints,
    socketpairs, hardlinks, no hardlink-specials, symlinks, IPv6, atimes,
    batchfiles, inplace, append, ACLs, xattrs, optional protect-args, iconv,
    symtimes, prealloc, stop-at, no crtimes
Optimizations:
    no SIMD, asm, openssl-crypto
Checksum list:
    xxh128 xxh3 xxh64 (xxhash) md5 md4 none
Compress list:
    zstd lz4 zlibx zlib none

rsync comes with ABSOLUTELY NO WARRANTY.  This is free software, and you
are welcome to redistribute it under certain conditions.  See the GNU
General Public Licence for details.
</code></pre>
<!--kg-card-end: markdown--><p>7. You are done. Enjoy!</p>]]></content:encoded></item><item><title><![CDATA[Can Apple Watch Measure Your Body Weight? [Updated for 2022]]]></title><description><![CDATA[How does my apple watch know my weight?]]></description><link>https://ayewo.com/can-the-apple-watch-measure-your-body-weight/</link><guid isPermaLink="false">6499621cf941aa2920349d15</guid><category><![CDATA[Personal]]></category><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Sun, 19 Sep 2021 13:12:32 GMT</pubDate><media:content url="https://ayewo.com/content/images/2021/09/solen-feyissa-15v6smjHVHQ-unsplash_medium-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://ayewo.com/content/images/2021/09/solen-feyissa-15v6smjHVHQ-unsplash_medium-1.jpg" alt="Can Apple Watch Measure Your Body Weight? [Updated for 2022]"><p>As you can see in the screen shot below, the Apple Health app shows a fairly accurate measure of my weight that was added by the Apple Watch. Can the Apple Watch estimate a person&apos;s weight now? What&apos;s going on?</p><figure class="kg-card kg-image-card"><img src="https://ayewo.com/content/images/2021/09/Apple-Watch-Series-6_setup.png" class="kg-image" alt="Can Apple Watch Measure Your Body Weight? [Updated for 2022]" loading="lazy" width="1242" height="2208" srcset="https://ayewo.com/content/images/size/w600/2021/09/Apple-Watch-Series-6_setup.png 600w, https://ayewo.com/content/images/size/w1000/2021/09/Apple-Watch-Series-6_setup.png 1000w, https://ayewo.com/content/images/2021/09/Apple-Watch-Series-6_setup.png 1242w" sizes="(min-width: 720px) 720px"></figure><h2 id="tl-dr">TL;DR</h2><p>The short answer is <strong>no</strong>. The <strong>Apple Watch</strong> is not equipped with sensors for measuring body weight, at least not yet. The rest of this post explains why a fairly accurate reading of your body weight in the Apple Health app was added by your new Apple Watch.</p><h2 id="apple-watch-series-6">Apple Watch Series 6</h2><p>I recently got an new Apple Watch Series 6 which I finished setting up in early August, but I was surprised to see a fairly accurate measure of my weight in the Health app that was added by the Apple Watch, more than a week after I set up the Apple Watch for the first time.</p><p>I have a habit of measuring my weight at the end of my workouts using a regular body weight scale i.e. it is not a smart scale, but the day the entry was added by the AW was actually the first time I wore the AW prior to a full workout. In fact, the time the entry was added was during my warm ups&#x2014;stretches that I do prior to that day&apos;s workout to stave off injuries. At the time, I noticed that the LED lights at the back of the AW turned on a few times. The LED lights are used to <a href="https://support.apple.com/en-us/HT204666?ref=ayewo.com">monitor heart rate</a>.</p><h2 id="searching-for-clues">Searching for Clues</h2><p>So, I went searching online to see if the ability to measure, or at least estimate a wearer&apos;s weight is a hidden feature of the AW. If this were true, I&apos;d be curious to learn how what sensors are packed into the AW to enable such a neat feature. </p><p>After much searching, I came back empty handed.</p><h2 id="a-plausible-explanation">A Plausible Explanation</h2><p>Yesterday, I repeated the search using a slightly better set of keywords (in hindsight): &quot;apple watch series 6 measure weight&quot; and the 8th link on Google was to this MacRumors <a href="https://forums.macrumors.com/threads/can-apple-watch-measure-our-weight.1865651/?ref=ayewo.com">forum discussion</a> from 2015 where <a href="https://forums.macrumors.com/members/nicho.162544/?ref=ayewo.com">nicho</a> posted this helpful answer:</p><blockquote>If I remember right, when you <strong>set up</strong> your watch it <strong>checks your height and weight</strong> and such. It then puts that info into the health app.</blockquote><p>Essentially, what nicho is saying is that, as part of the first time set up dance, the AW does three things, it:</p><ul><li>reads your existing height and weight data stored in Apple Health;</li><li>uses the existing height and weight data for some calculations;</li><li>adds back your existing height and weight data to Apple Health.</li></ul><p>Looking more closely at the screenshot, the weight added by the AW is really just the previous day&apos;s reading, which I added manually to the Health app. I have gone on several more workouts with the AW on my wrist and no additional estimates of my weight have been added by the AW, since that one entry.</p><p>I checked Body Measurements -&gt; Weight, to look more closely at the data inside the Health app and the only difference between the existing data and the data added by the AW is that the name of the AW is recorded as the &quot;Source&quot; of the data in the Health app, while the &quot;Was User Entered&quot; flag is set to &quot;No&quot;.</p><p>I also checked Body Measurements -&gt; Height, and the AW did in fact add a new entry on that date and time for my height, similar to what it did for my weight.</p><p>So, my guess is that the AW didn&apos;t finish <strong>setting itself</strong> up the first time I wore it, until I triggered something (the hear rate sensor?)&#x2014;which was several days later.</p><p>Mystery solved!</p><h2 id="the-newer-apple-watch-models-have-more-sensors">The Newer Apple Watch Models Have More Sensors</h2><p>At the Apple Event on September 7, 2022, Apple announced updates to the Apple Watch product line where a new sensor for <a href="https://support.apple.com/en-us/HT213275?ref=ayewo.com">wrist temperature sensing</a> was added to two brand new Apple Watch models:</p><ul><li><a href="https://www.apple.com/apple-watch-series-8/why-apple-watch/?ref=ayewo.com">Apple Watch Series 8</a>;</li><li><a href="https://www.apple.com/apple-watch-ultra/why-apple-watch/?ref=ayewo.com">Apple Watch Ultra</a>.</li></ul><p>This is a cool new feature but there&apos;s still no support for body weight estimation using only an Apple Watch.</p>]]></content:encoded></item><item><title><![CDATA[RE: Apple, Its Control Over the iPhone, The Internet, And The Metaverse]]></title><description><![CDATA[<p><em>Note to readers: This is a rejoinder to <a href="https://twitter.com/ballmatthew?ref=ayewo.com">Matthew Ball</a>&apos;s epic 15000-word <a href="https://www.matthewball.vc/all/applemetaverse?ref=ayewo.com">essay of the same title</a>, published on February 2, 2021. He was gracious enough to <a href="https://twitter.com/ballmatthew/status/1358876311041761285?ref=ayewo.com">reply</a> to my initial concerns on Twitter, and he did stop by to read this.</em></p><p>It is not often that someone</p>]]></description><link>https://ayewo.com/re-apple-its-control-over-the-iphone-the-internet-and-the-metaverse/</link><guid isPermaLink="false">6499621cf941aa2920349d12</guid><dc:creator><![CDATA[Saïd]]></dc:creator><pubDate>Tue, 09 Feb 2021 17:12:49 GMT</pubDate><media:content url="https://ayewo.com/content/images/2021/02/Add-a-heading.png" medium="image"/><content:encoded><![CDATA[<img src="https://ayewo.com/content/images/2021/02/Add-a-heading.png" alt="RE: Apple, Its Control Over the iPhone, The Internet, And The Metaverse"><p><em>Note to readers: This is a rejoinder to <a href="https://twitter.com/ballmatthew?ref=ayewo.com">Matthew Ball</a>&apos;s epic 15000-word <a href="https://www.matthewball.vc/all/applemetaverse?ref=ayewo.com">essay of the same title</a>, published on February 2, 2021. He was gracious enough to <a href="https://twitter.com/ballmatthew/status/1358876311041761285?ref=ayewo.com">reply</a> to my initial concerns on Twitter, and he did stop by to read this.</em></p><p>It is not often that someone takes effort to pen a long-form piece of about 30-pages on Apple (Matthew says the essay took <a href="https://twitter.com/ballmatthew/status/1356768374924201987?ref=ayewo.com">several months</a> to write), but because it concerns Apple, the details matter. </p><p>I have never worked with or for Apple, neither do I own Apple stock, but as someone who has been in the industry for long enough, I can say that Apple would easily be at the top of a list of the most detailed-oriented companies, if such a list were to exist. Perhaps this is also why their actions and in-actions are grossly misunderstood.</p><p></p><p><strong>Chapter One: The Creation of Today&#x2019;s Internet and the Needs of Tomorrow&#x2019;s</strong></p><p>In chapter one, Matthew writes that:</p><blockquote>Services like Zoom also work because they leverage actively maintained standards that are free to use and designed to support any device.</blockquote><p>I totally understand that this essay may have been written to be accessible to a lay person, but the problem with this claim is that, it doesn&apos;t tell the full story.</p><p>A lot of video conference call invites are accepted via email, and if you are doing it for the first time, you&apos;ll likely not have the Zoom client installed on your machine, so yes, Zoom has to &quot;leverage actively maintained standards&quot; to make a good first impression, when all a first-time user has installed is a web browser. </p><p>But as your needs expand, so will your desire to explore the best they have to offer&#x2013;at which point they can nudge you towards their <a href="https://blog.zoom.us/zoom-can-provide-increase-industry-leading-video-capacity/?ref=ayewo.com">more powerful services</a>, which are of course not built on (open) standards.</p><p></p><p>He also writes that:</p><blockquote>But just imagine, for example, how the internet might differ if it had been created by multinational media conglomerates in order to sell things, serve ads or harvest user data for profits. Downloading a .JPG could cost money, with a .PNG costing 50% more. Teleconferencing software might have required the use of a broadband operator&#x2019;s app or portal (e.g. Welcome to your Xfinity Browser&#x2122;, click here for Xfinitybook&#x2122; or XfinityCalls&#x2122; powered by Zoom&#x2122;). Websites might only work in Internet Explorer <em>or</em> Chrome - and need to pay a given browser an annual fee for the privilege.</blockquote><p>The fascinating thing about this passage is that, every single hypothetical idea he doesn&apos;t wish to entertain has been used in the past, successfully. </p><p>Yes, downloading a .JPG or images in general, did in fact cost money when Internet usage wasn&apos;t yet wide spread. Although, you didn&apos;t pay for each image directly, the cost of your web browsing was reflected in your phone bill, because back then, the main way to access the Internet was via phone modems.</p><p>To corroborate what I&apos;m saying, here&apos;s a June 28, 1994 entry on the topic from the <a href="https://www.nytimes.com/1994/06/28/science/personal-computers-you-can-t-roller-skate-on-electronic-highway.html?ref=ayewo.com">NY Times</a> titled <em>PERSONAL COMPUTERS; You Can&apos;t Roller-Skate On Electronic Highway. </em>Below is an excerpt:</p><blockquote>Although the general rule in modems is &quot;the faster the better,&quot; there are several reasons a cautious computer user might wisely choose a slower 14.4 kilobit-per-second modem over the V.34 speedsters.</blockquote><blockquote>The first is cost. As with any new technology, the pioneers pay the price. The new modems typically cost $500 or more, as against $100 to $150 for the 14.4-kilobit modems.</blockquote><p>Due to the cost implications, it was not uncommon to browse the Web image-free via the &quot;Do not show any images&quot; browser setting, especially because a lot of dial-up modems were very slow anyway.</p><p>If you substitute Xfinity with <a href="https://en.wikipedia.org/wiki/AOL?ref=ayewo.com">AOL</a>, the hypothetical scenario he paints about portals was in fact the lived experience for millions of first-time Internet users, as succinctly captured by this <a href="https://www.nytimes.com/1996/08/14/business/follow-up-survey-reports-growth-in-internet-users.html?ref=ayewo.com">NY Times article</a> from 1996:</p><blockquote>But the follow-up study also found that 20 percent of the people who said they had access to the Internet in the first survey said they no longer had access six months later. Of those who did visit the Internet, most did so through commercial on-line services like America Online, the study found. America Online promises to connect users with a minimum of technical difficulty, which attracts inexperienced computer users.</blockquote><blockquote>&apos;&apos;It makes sense, because the usage of people who go on line through a commercial service tends to be more personal and more periodic,&apos;&apos; said Thomas E. Miller, vice president of the emerging technologies research group of Find/SVP, a market research firm in New York. &apos;&apos;AOL has succeeded in penetrating a downscale audience.&apos;&apos;</blockquote><p></p><p>With respect to web browsers:</p><blockquote>Websites might only work in Internet Explorer <em>or</em> Chrome - </blockquote><p><a href="https://www.technologizer.com/2010/09/16/the-unwelcome-return-of-best-viewed-with-internet-explorer/?ref=ayewo.com">Best viewed with Internet Explorer</a> was an actual thing, it even had a logo to go with it that should be instantly recognizable to early netizens.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ayewo.com/content/images/2021/02/01_Best-viewed-with-Internet-Explorer_bestie.jpg" class="kg-image" alt="RE: Apple, Its Control Over the iPhone, The Internet, And The Metaverse" loading="lazy" width="148" height="124"><figcaption><a href="https://i2.wp.com/www.technologizer.com/wp-content/uploads/2010/09/bestie.jpg?ref=ayewo.com">Best viewed with Internet Explorer badge</a></figcaption></figure><p></p><blockquote>... and need to pay a given browser an annual fee for the privilege.</blockquote><p>Today, Matthew may balk at the idea of paying for a web browser, but people back then certainly didn&apos;t. Many may know Marc Andreessen as promoter of VC firm <a href="https://a16z.com/?ref=ayewo.com">Andreessen Horowitz (a16z)</a>, but in the Feb. 19, 1996 issue of <a href="http://content.time.com/time/covers/0,16641,19960219,00.html?ref=ayewo.com">Time Magazine</a>, he appeared on the cover. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ayewo.com/content/images/2021/02/02_Marc-Andreessen_1101960219_400.jpg" class="kg-image" alt="RE: Apple, Its Control Over the iPhone, The Internet, And The Metaverse" loading="lazy" width="400" height="527"><figcaption><a href="http://img.timeinc.net/time/magazine/archive/covers/1996/1101960219_400.jpg?ref=ayewo.com">Barefoot Marc Andreessen on the cover of Time</a></figcaption></figure><p>What was his claim to fame? You guessed it&#x2013;a successful IPO from an Internet business that initially made money from <a href="https://www.nytimes.com/1995/06/05/business/netscape-to-sell-internet-browsers.html?ref=ayewo.com">selling web browsers for $40 a pop</a>!</p><p></p><p><strong>Chapter Two - Three</strong></p><p>Similar to Chapter 1, there are several historical inaccuracies in Chapters 2 - 3.</p><p>It&apos;s okay to make mistakes since no one can know everything about our computing past. I&apos;ll skip them, particularly because it is merely part of the build up to the meat of the essay. Besides, I&apos;m trying to avoid making this piece too long.</p><p></p><p><strong>Chapter Four: Apple&#x2019;s Deficient Monopoly Defenses</strong></p><blockquote>Lastly, it&#x2019;s notable that while MacOS does not have iOS&#x2019;s restrictions on software/app installations, it remains secure and safe to use. This is because the majority of security is held at the kernel/OS-level. To this end, <a href="https://www.forbes.com/sites/zakdoffman/2019/10/24/new-iphone-threat-these-17-malicious-apps-may-be-on-your-devicedelete-them-now/?sh=29ab5ea553e3&amp;ref=ayewo.com">malicious apps (and updates)</a> have lengthy history of making it through the App Store review. This is inevitable given the volume of submissions, time spent reviewing code, and human error rates, but crucially, these bad actor apps don&#x2019;t destroy the device or devour private files (as is often the case with Windows malware). This is because of iOS&#x2019;s system and API-level security, which is a stronger and more scalable solution - and one that offers consumers more choice and developers more capabilities.</blockquote><p>The reason why there&apos;s a disparity in security incidents between macOS and iOS isn&apos;t far fetched. As far as devices go, there are way more iOS users than there are macOS users, which causes attackers to focus on iOS because of the incredible ROI.</p><p>It is true that iOS doesn&apos;t allow third-party browser engines, but there are tricky engineering details behind why Apple made this trade off. I&apos;d love to delve into it here, but based on the outline I wrote, it deserves to be written as a standalone piece. Including it here would make this rejoinder nearly as long as the original ~30-page essay.</p><p></p><p><strong>Chapter Five: The Importance of Prioritizing Overall Prosperity</strong></p><blockquote>Why does this matter? How much should we really care about how apps are made and who gets paid for them? Or the platforms that are used or allowed? Or who can advantage which identity solution, service, or standard? The answer is that we have to care. In 2021, almost every company is a digital company, and the scope, significance, and complexity of the digital/virtual world continues to expand.</blockquote><blockquote>To prosper, new winners must be able to emerge at every layer of the &#x201C;new economy&#x201D; and without being constrained by what a single company envisions or wants.<em> </em>The current situation isn&#x2019;t terrible &#x2014; great things are happening in terms of users, creators, and innovation. But the very decisions that have made Apple so successful today also limit the number of creators that participate in the virtual economy, handicap the creativity of their products and business models, and make every transaction more expensive. Most important, it is blocking the organic evolution of the overall Internet.</blockquote><p>I don&apos;t understand the logic here. Let&apos;s zoom out by focusing on another technology company that makes physical goods and is using an integrated approach: Tesla. </p><p>Tesla, like Apple, tries to own every part of the experience: the car, the (expensive) battery, the car charging network, the in-car operating system called <a href="https://www.tesla.com/blog/introducing-software-version-10-0?ref=ayewo.com">Tesla Software</a> (a corollary to iOS), a soon to be launched app store, and of course they own what would have been the car distribution via traditional car dealerships.</p><p>Similar to Apple, there is one exception to this integrated approach that they do not try to own or control: the ecosystem around accessories. Tesla has a growing ecosystem for accessories:</p><ul><li><a href="https://abstractocean.com/?ref=ayewo.com">Abstract Ocean</a></li><li><a href="https://evannex.com/?ref=ayewo.com">Evannex</a></li><li><a href="https://www.rpmtesla.com/?ref=ayewo.com">RPM Tesla</a></li></ul><p>Today, Elon Musk is worth <a href="https://www.forbes.com/profile/elon-musk/?ref=ayewo.com">$186.5B</a>, a large part is due to his Tesla holding, and if they continue on their current trajectory (read: their competitors take too long to figure out electric in a way that is viable), they might go on to dominate not just the electric car, but the car industry as a whole.</p><p>The Tesla in-car OS currently supports apps for <a href="https://www.tesla.com/support/software-version-10-0?ref=ayewo.com">Spotify</a>, Netflix, YouTube, and Hulu, and I imagine that list will continue to grow. Assuming they go on to dominate the car industry, somehow by some logic that is not clear to me, they should be compelled to allow on all Teslas, third-party OSes or third-party app stores that compete with Tesla OS or its app store?</p><p>In other words, <a href="https://www.apple.com/ios/carplay/?ref=ayewo.com">Apple CarPlay</a> and <a href="https://www.android.com/auto/?ref=ayewo.com">Android Auto</a>, which are competing car OSes designed for general purpose cars (each with its own marketplace), should somehow be allowed to be installed on Teslas because Tesla is hypothetically the dominant car maker, but not on competitor cars because those are not monopolies? </p><p>Tesla&apos;s hypothetical blocking of third-party app stores would somehow be &quot;blocking the organic evolution of the overall car industry&quot;? Even though they are not the only car seller, they just happen to be the most dominant? Which law or moral code would they be violating? </p><p>No car-maker has ever been subjected to such logic, at least with respect to their in-car OS, so I don&apos;t understand this logic at all or how it should somehow apply to Apple&apos;s iOS. </p><p>If Apple&apos;s iOS wielded a monopoly on <em>all mobile devices</em>, irrespective of device maker similar to Microsoft Windows at its peak, then this would be a clear case of monopoly power being wielded that needs to be curbed.</p><p>I suspect people are leaning too much on the <em>desktop</em> PC and its attendant metaphors, to reason about how the ecosystem should be around <em>mobile</em> products.</p><p></p><p><strong>Chapter Six: Solving the Apple Problem</strong></p><blockquote>Apple has the right to run its own store, offer its own standards, and develop services that are exclusive to its hardware. The problems arise from Apple&#x2019;s forced bundling of hardware, an operating system, distribution system, payment solution and services. As a result, there is a straightforward remedy &#x2014; forcing Apple into competition in app distribution and payments.</blockquote><p>I&apos;m not sure I understand what is being advocated here. Apple&apos;s operating philosophy has always been to offer an integrated experience as opposed to the less integrated experience of <a href="https://en.wikipedia.org/wiki/Wintel?ref=ayewo.com">Wintel</a>; where Microsoft makes the operating system (OS), Intel makes the computer chip, while Original Equipment Manufacturers or OEMs make the rest of the computer.</p><p>This is not some new approach, it has always been how they approach the design their products. In fact, at the <a href="https://www.youtube.com/watch?v=XAfTXYa36f4&amp;ref=ayewo.com">2007 launch of the iPhone</a> (0:40s YouTube clip), Steve Jobs attributed how they think about technology products to a quote from 40 years ago (Apple is 45 years old) by <a href="https://en.wikipedia.org/wiki/Alan_Kay?ref=ayewo.com">Alan Kay</a>, a highly respected computing pioneer:</p><blockquote><em>People who are really serious about software should make their own hardware. &#x2013; Alan Kay</em></blockquote><p>When the dominant metaphor was desktop computing, Apple competed against Windows on the desktop by offering an integrated experience and <a href="https://www.nytimes.com/2018/08/02/technology/apple-stock-1-trillion-market-cap.html?ref=ayewo.com">nearly went bankrupt in 1997</a>. The market voted with their wallets because they favored the less integrated approach of <a href="https://en.wikipedia.org/wiki/Wintel?ref=ayewo.com">Wintel</a>. Of course, there were a lot of factors at play.</p><p>Today, the dominant metaphor is mobile computing. In 2007, Apple was the underdog with 0% market share in mobile, but it didn&apos;t deter them from using the same integrated approach against two massive incumbents&#x2013;smartphone vendors Nokia and BlackBerry.</p><p>Nokia, with the largest market share, made the device, open-source <a href="https://en.wikipedia.org/wiki/Symbian?ref=ayewo.com">OS</a>, <a href="https://en.wikipedia.org/wiki/Ovi_(Nokia)?ref=ayewo.com">app store</a> and allowed the loading of apps from third-party app stores&#x2013;if you know how.</p><p>BlackBerry, with the next largest market share, also made the device, proprietary <a href="https://en.wikipedia.org/wiki/BlackBerry_OS?ref=ayewo.com">OS</a> and <a href="https://en.wikipedia.org/wiki/BlackBerry_World?ref=ayewo.com">app store</a>. They didn&apos;t officially allow loading of apps from third-party app stores, but like Nokia, the process was <a href="https://www.lemmymorgan.com/install-3rd-party-apps-to-blackberry/?ref=ayewo.com">convoluted</a> anyway.</p><p>Each had a lot of similarities to Apple&apos;s integrated approach of software, hardware and services, but Nokia and BlackBerry lost to Apple, not because they were a monopoly (they were the underdog), but because the market valued Apple&apos;s interpretation of what an integrated experience should look like more than theirs. Simple market economics is at play here, nothing else.</p><p>Users who value the existence of a viable third-party app store ecosystem over an integrated experience can always use Google Android. Anyone else who feels left out, can always <a href="https://www.theverge.com/2017/8/18/16165040/essential-phone-review-android-andy-rubin?ref=ayewo.com">seek VC funding</a> to start an Android or iPhone killer, and let the market decide.</p>]]></content:encoded></item></channel></rss>