<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://silvae86.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://silvae86.github.io/" rel="alternate" type="text/html" /><updated>2024-10-21T14:20:24+00:00</updated><id>https://silvae86.github.io/feed.xml</id><title type="html">The caffeinated engineer</title><subtitle>João Rocha da Silva, Software Engineer</subtitle><author><name>João Rocha da Silva</name></author><entry><title type="html">Tethering internet from iPhone to a Ubuntu machine and then to a wifi router via ethernet</title><link href="https://silvae86.github.io/2024/10/20/connect-iphone-to-ubuntu-machine-tether-ethernet/" rel="alternate" type="text/html" title="Tethering internet from iPhone to a Ubuntu machine and then to a wifi router via ethernet" /><published>2024-10-20T22:15:00+00:00</published><updated>2024-10-20T22:15:00+00:00</updated><id>https://silvae86.github.io/2024/10/20/connect-iphone-to-ubuntu-machine-tether-ethernet</id><content type="html" xml:base="https://silvae86.github.io/2024/10/20/connect-iphone-to-ubuntu-machine-tether-ethernet/"><![CDATA[<p class="float-img-center">
	<a href="/assets/images/post-images/2024-10-21-connect-iphone-to-ubuntu-machine-tether-ethernet/setup.jpg" class="imglightbox" data-description="Picture of the setup" data-title="Picture of the setup">
		<img src="/assets/images/post-images/2024-10-21-connect-iphone-to-ubuntu-machine-tether-ethernet/setup.jpg" alt="Picture of the setup" />	
	</a><br />
	<em>Picture of the setup</em></p>

<p>Travelling with an iPhone as a hotspot is good enough in a pinch, but the signal is weak and will cause disconnections if used as the main Access Point in a home.</p>

<p>This post explains how to connect an iPhone to a Ubuntu machine via usb and then share the internet connection to a wifi router via the ethernet port of that machine.</p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2024-10-21-connect-iphone-to-ubuntu-machine-tether-ethernet/network.png" class="imglightbox" data-description="iPhone + Ubuntu box + Router network" data-title="iPhone + Ubuntu box + Router network">
		<img src="/assets/images/post-images/2024-10-21-connect-iphone-to-ubuntu-machine-tether-ethernet/network.png" alt="iPhone + Ubuntu box + Router network" />	
	</a><br />
	<em>iPhone + Ubuntu box + Router network</em></p>

<p>I am using a <a href="https://www.tp-link.com/in/home-networking/wifi-router/tl-wr1502x/">TP-Link WR1502X Travel Router</a>. The router is supposed to have built-in USB tethering, but it did not work with my iPhone 15 Pro running iOS 17. An older iPhone SE 2020 with iOS 16 works, so it goes to show how reliable these USB tethering things are. Putting an <a href="https://support.hp.com/id-en/product/details/hp-t630-thin-client/10522151">HP T630 Thin Client</a> to act as our Gateway via ethernet will fix this.</p>

<h2 id="configuring-the-linux-pc">Configuring the Linux PC</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre></td><td class="rouge-code"><pre><span class="c"># see these values with `sudo lshw -class network -short`</span>
<span class="nv">IPHONE_NIC</span><span class="o">=</span><span class="s1">'enxd26b7855052c'</span> <span class="c"># the iphone's Ethernet "virtual connection"</span>
<span class="nv">ETHERNET_NIC</span><span class="o">=</span><span class="s1">'enp1s0'</span> <span class="c"># your network card where you'll connect the router via cable</span>


<span class="c"># Enable IP Forwarding sysctl -w net.ipv4.ip_forward=1. This will enable the kernel to forward packets, which are arriving to this machine.</span>
<span class="nb">sudo </span>sysctl <span class="nt">-w</span> net.ipv4.ip_forward<span class="o">=</span>1

<span class="c"># Assign a static IP within the same mask as the router's DHCP pool, but that you will later assign as a reserved IP in the DHCP pool</span>
<span class="nb">sudo </span>ifconfig <span class="nv">$ETHERNET_NIC</span> 192.168.0.253 netmask 255.255.255.0 up

<span class="c"># install persistent iptables to make the forwarding -&gt; masquerading permanent</span>
<span class="nb">sudo </span>apt <span class="nb">install</span> <span class="nt">-y</span> iptables-persistent
<span class="nb">sudo </span>systemctl <span class="nb">enable </span>netfilter-persistent.service
<span class="nb">sudo </span>systemctl status netfilter-persistent.service

<span class="c"># Enable masquerading on the interface which is connected to the internet. sudo iptables -t nat -A POSTROUTING -o $IPHONE_NIC -j MASQUERADE. This will masquerade (replace the src ip on the packet with the $IPHONE_NIC ip) all traffic arriving from other interfaces, to the $IPHONE_NIC interface.</span>
<span class="nb">sudo </span>iptables <span class="nt">-t</span> nat <span class="nt">-A</span> POSTROUTING <span class="nt">-o</span> <span class="nv">$IPHONE_NIC</span> <span class="nt">-j</span> MASQUERADE

<span class="c"># Add iptable rules to ACCEPT and FORWARD traffic from the subnet</span>
<span class="nb">sudo </span>iptables <span class="nt">-I</span> FORWARD <span class="nt">-o</span> <span class="nv">$IPHONE_NIC</span> <span class="nt">-s</span> 192.168.0.0/16 <span class="nt">-j</span> ACCEPT
<span class="nb">sudo </span>iptables <span class="nt">-I</span> INPUT <span class="nt">-s</span> 192.168.0.0/16 <span class="nt">-j</span> ACCEPT

<span class="c"># Make changes persistent across boots</span>
<span class="nb">sudo </span>iptables-save | <span class="nb">sudo tee</span> /etc/iptables/rules.v4
</pre></td></tr></tbody></table></code></pre></div></div>

<h1 id="router-configuration">Router configuration</h1>

<p>Router will be at <a href="http://192.168.0.1">http://192.168.0.1</a> when you are connected to its Wifi network.</p>

<ol>
  <li>Put router in router mode (physical slider)</li>
  <li>Put ethernet cable in LAN port and connect to linux host ethernet port</li>
  <li>Access <a href="http://192.168.0.1">http://192.168.0.1</a></li>
  <li>Network tab will show error, never mind that</li>
  <li>Internet tab
 	- Type: Router (Current)
 	- Internet connection type -&gt; Dynamic IP
 	- Use Default MAC address</li>
  <li>Wifi configure how you want (SSID + PASS)</li>
  <li>
    <p>Advanced
 	- Internet Connection Type -&gt; Click Renew lease
  	- LAN - IP Address -&gt; 192.168.0.1</p>
  </li>
  <li>DHCP Server 
     - Enable
 	- IP Address pool - 192.168.0.2 - 192.168.0.100
 	- Default Gateway - 192.168.0.253 (Static IP in Linux Machine’s ethernet port)
 	- Primary DNS: 8.8.8.8
 	- Secondary DNS: 8.8.4.4
 	- Address Reservation -&gt; hotspot (linux machine)
 	        - MAC ADDRESS: Computer’s Ethernet MAC address (use <code class="language-plaintext highlighter-rouge">ifconfig</code> to see it)
 	        - IP Address: 192.168.0.253</li>
</ol>

<h2 id="references">References</h2>

<p><a href="https://medium.com/@TarunChinmai/sharing-internet-connection-from-a-linux-machine-over-ethernet-a5cbbd775a4f">Sharing internet connection from a linux machine over Ethernet</a>.</p>]]></content><author><name>João Rocha da Silva</name></author><category term="iphone" /><category term="internet" /><category term="tethering" /><category term="ubuntu" /><category term="tp-link" /><category term="AX1500" /><category term="wifi-6" /><category term="WR1502X" /><category term="travel" /><category term="router" /><summary type="html"><![CDATA[Picture of the setup]]></summary></entry><entry><title type="html">Lenovo P1 Gen 2 - My settings for the TrackPoint and Synaptics Trackpad</title><link href="https://silvae86.github.io/2023/05/10/trackpoint-synaptics-trackpad-settings-in-ubuntu-23-04-lenovo-thinkpad-p1-gen2/" rel="alternate" type="text/html" title="Lenovo P1 Gen 2 - My settings for the TrackPoint and Synaptics Trackpad" /><published>2023-05-10T21:00:00+00:00</published><updated>2023-05-10T21:00:00+00:00</updated><id>https://silvae86.github.io/2023/05/10/trackpoint-synaptics-trackpad-settings-in-ubuntu-23-04-lenovo-thinkpad-p1-gen2</id><content type="html" xml:base="https://silvae86.github.io/2023/05/10/trackpoint-synaptics-trackpad-settings-in-ubuntu-23-04-lenovo-thinkpad-p1-gen2/"><![CDATA[<p>This post explains how to install the Synaptics driver on Ubuntu 23.04 and how to tune the sensitivity settings on a Lenovo P1 Gen 2.</p>

<h2 id="replace-libinput-touchpad-driver-with-the-synaptics-driver">Replace libinput touchpad driver with the Synaptics driver</h2>

<p>I find that the Synaptics touchpad driver for the P1 performs better than the default one. If you are happy with it, skip everything regarding the Trackpad.</p>

<p>First, go <a href="https://www.synaptics.com/products/displaylink-graphics/downloads/ubuntu">here</a> and install the synaptics driver for the touchpad. You will be prompted for a password that you need to enter again on a blue screen that will show up after the first reboot, since this driver lives in the UEFI.</p>

<h2 id="install-event-device-driver-for-the-trackpoint">Install Event Device driver for the TrackPoint</h2>

<p>We will be replacing libinput with evdev driver for the trackpoint. It has been around since the IBM laptop days, so this guarantees an even more <em>Vintage</em> experience:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span>apt-get <span class="nb">install </span>evdev
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="configuration-files">Configuration files</h2>

<p>You now need to place a few files in certain locations.</p>

<div class="alert alert-warning" role="alert">
<i class="fa fa-exclamation-triangle"></i> <b>Warning:</b>
Every time you make changes to these files, you will have to  log out and login again for settings to be applied.
</div>

<p><code class="language-plaintext highlighter-rouge">sudo vim /etc/X11/xorg.conf.d/70-synaptics.conf</code></p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
</pre></td><td class="rouge-code"><pre><span class="c"># Example xorg.conf.d snippet that assigns the touchpad driver
# to all touchpads. See xorg.conf.d(5) for more information on
# InputClass.
# DO NOT EDIT THIS FILE, your distribution will likely overwrite
# it when updating. Copy (and rename) this file into
# /etc/X11/xorg.conf.d first.
# Additional options may be added in the form of
#   Option "OptionName" "value"
#
</span><span class="n">Section</span> <span class="s2">"InputClass"</span>
        <span class="n">Identifier</span> <span class="s2">"touchpad catchall"</span>
        <span class="n">MatchIsTouchpad</span> <span class="s2">"on"</span>
<span class="c"># This option is recommend on all Linux systems using evdev, but cannot be
# enabled by default. See the following link for details:
# http://who-t.blogspot.com/2010/11/how-to-ignore-configuration-errors.html
</span>       	<span class="n">MatchDevicePath</span> <span class="s2">"/dev/input/event*"</span>
       	<span class="n">Option</span> <span class="s2">"PalmDetect"</span> <span class="s2">"1"</span>
       	<span class="n">Option</span> <span class="s2">"PalmMinWidth"</span> <span class="s2">"10"</span>
       	<span class="n">Option</span> <span class="s2">"PalmMinZ"</span> <span class="s2">"50"</span>
       	<span class="n">Option</span> <span class="s2">"HorizTwoFingerScroll"</span> <span class="s2">"1"</span>
	<span class="n">Option</span> <span class="s2">"MinSpeed"</span>              <span class="s2">"0.6"</span>
        <span class="n">Option</span> <span class="s2">"MaxSpeed"</span>              <span class="s2">"2.3"</span>
        <span class="n">Option</span> <span class="s2">"AccelFactor"</span>           <span class="s2">"0.11"</span>
        <span class="n">Option</span> <span class="s2">"TapButton1"</span>            <span class="s2">"1"</span>
        <span class="n">Option</span> <span class="s2">"TapButton2"</span>            <span class="s2">"2"</span>     <span class="c"># multitouch
</span>        <span class="n">Option</span> <span class="s2">"TapButton3"</span>            <span class="s2">"3"</span>     <span class="c"># multitouch
</span>        <span class="n">Option</span> <span class="s2">"VertTwoFingerScroll"</span>   <span class="s2">"1"</span>     <span class="c"># multitouch
</span>        <span class="n">Option</span> <span class="s2">"HorizTwoFingerScroll"</span>  <span class="s2">"1"</span>     <span class="c"># multitouch
</span>        <span class="n">Option</span> <span class="s2">"VertEdgeScroll"</span>        <span class="s2">"1"</span>
        <span class="n">Option</span> <span class="s2">"CoastingSpeed"</span>         <span class="s2">"0"</span>
        <span class="n">Option</span> <span class="s2">"CornerCoasting"</span>        <span class="s2">"1"</span>
        <span class="n">Option</span> <span class="s2">"CircularScrolling"</span>     <span class="s2">"1"</span>
        <span class="n">Option</span> <span class="s2">"CircScrollTrigger"</span>     <span class="s2">"7"</span>
        <span class="n">Option</span> <span class="s2">"EdgeMotionUseAlways"</span>   <span class="s2">"1"</span>
        <span class="n">Option</span> <span class="s2">"LBCornerButton"</span>        <span class="s2">"8"</span>     <span class="c"># browser "back" btn
</span>        <span class="n">Option</span> <span class="s2">"RBCornerButton"</span>        <span class="s2">"9"</span>     <span class="c"># browser "forward" btn
</span><span class="n">EndSection</span>

<span class="n">Section</span> <span class="s2">"InputClass"</span>
        <span class="n">Identifier</span> <span class="s2">"touchpad ignore duplicates"</span>
        <span class="n">MatchIsTouchpad</span> <span class="s2">"on"</span>
        <span class="n">MatchOS</span> <span class="s2">"Linux"</span>
        <span class="n">MatchDevicePath</span> <span class="s2">"/dev/input/mouse*"</span>
        <span class="n">Option</span> <span class="s2">"Ignore"</span> <span class="s2">"on"</span>
<span class="n">EndSection</span>

<span class="c"># This option enables the bottom right corner to be a right button on clickpads
# and the right and middle top areas to be right / middle buttons on clickpads
# with a top button area.
# This option is only interpreted by clickpads.
</span><span class="n">Section</span> <span class="s2">"InputClass"</span>
        <span class="n">Identifier</span> <span class="s2">"Default clickpad buttons"</span>
        <span class="n">MatchDriver</span> <span class="s2">"synaptics"</span>
        <span class="n">Option</span> <span class="s2">"SoftButtonAreas"</span> <span class="s2">"50% 0 82% 0 0 0 0 0"</span>
        <span class="n">Option</span> <span class="s2">"SecondarySoftButtonAreas"</span> <span class="s2">"58% 0 0 15% 42% 58% 0 15%"</span>
<span class="n">EndSection</span>

<span class="c"># This option disables software buttons on Apple touchpads.
# This option is only interpreted by clickpads.
</span><span class="n">Section</span> <span class="s2">"InputClass"</span>
        <span class="n">Identifier</span> <span class="s2">"Disable clickpad buttons on Apple touchpads"</span>
        <span class="n">MatchProduct</span> <span class="s2">"Apple|bcm5974"</span>
        <span class="n">MatchDriver</span> <span class="s2">"synaptics"</span>
        <span class="n">Option</span> <span class="s2">"SoftButtonAreas"</span> <span class="s2">"0 0 0 0 0 0 0 0"</span>
<span class="n">EndSection</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">sudo vim /etc/X11/xorg.conf.d/90-trackpoint.conf</code></p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
</pre></td><td class="rouge-code"><pre><span class="c"># Section "InputClass"  
#    Identifier "libinput pointer catchall"  
#    MatchIsPointer "on"  
#    MatchDevicePath "/dev/input/event*"  
#    Driver "libinput"  
#    Option "AccelSpeed" "-1.25"  
# EndSection  
</span>
<span class="n">Section</span> <span class="s2">"InputClass"</span>
    <span class="n">Identifier</span> <span class="s2">"Trackpoint Settings"</span>
    <span class="n">MatchProduct</span> <span class="s2">"TPPS/2 Elan TrackPoint"</span>
    <span class="n">MatchDevicePath</span> <span class="s2">"/dev/input/event*"</span>
    <span class="n">Driver</span> <span class="s2">"evdev"</span>
    <span class="n">Option</span> <span class="s2">"EmulateWheel"</span> <span class="s2">"true"</span>
    <span class="n">Option</span> <span class="s2">"EmulateWheelButton"</span> <span class="s2">"2"</span>
    <span class="c"># Option "Emulate3Buttons" "true"
</span>    <span class="n">Option</span> <span class="s2">"EmulateWheelInertia"</span>  <span class="err">"</span><span class="m">10</span><span class="err">'</span>
    <span class="n">Option</span> <span class="s2">"EmulateWheelTimeOut"</span> <span class="s2">"200"</span>
    <span class="n">Option</span> <span class="s2">"Emulate3Timeout"</span> <span class="s2">"50"</span>
    <span class="n">Option</span> <span class="s2">"XAxisMapping"</span> <span class="s2">"6 7"</span>
    <span class="n">Option</span> <span class="s2">"YAxisMapping"</span> <span class="s2">"4 5"</span>
    <span class="n">Option</span> <span class="s2">"ButtonMapping"</span> <span class="s2">"1 0 3 4 5 6 7"</span>


<span class="c"># Set up an acceleration config
</span>    <span class="n">Option</span>   <span class="s2">"VelocityScale"</span>           <span class="s2">"5"</span>
    <span class="n">Option</span>   <span class="s2">"AccelerationProfile"</span>     <span class="s2">"7"</span>
    <span class="n">Option</span>   <span class="s2">"AccelerationNumerator"</span>   <span class="s2">"14"</span>
    <span class="n">Option</span>   <span class="s2">"AccelerationDenominator"</span> <span class="s2">"3"</span>
    <span class="n">Option</span>   <span class="s2">"ConstantDeceleration"</span>  <span class="s2">"2"</span>
    <span class="n">option</span>   <span class="s2">"AdaptiveDeceleration"</span>  <span class="s2">"3"</span>
    <span class="n">Option</span>   <span class="s2">"AccelerationScheme"</span> <span class="s2">"predictable"</span>
    <span class="n">Option</span>   <span class="s2">"AccelerationThreshold"</span> <span class="s2">"6"</span>
<span class="n">EndSection</span>
 
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="gnome-settings">Gnome settings</h2>

<ul>
  <li>In the Gnome Extensions app, go to “Keyboard and Mouse” and make sure that the mouse acceleration profile is “Flat”.</li>
  <li>In the Gnome Setting app, make sure that the trackpad and mouse speed sliders are in the default (middle) position.</li>
</ul>

<h2 id="references">References</h2>

<p><a href="https://forums.opensuse.org/t/how-do-i-set-acceleration-on-tpps-2-ibm-trackpoint/122699/16">How do I set acceleration on “TPPS/2 IBM TrackPoint” ?</a>.</p>

<p><a href="https://www.reddit.com/r/thinkpad/comments/5rcwlq/heres_how_to_get_the_perfect_trackpoint/">How to get the perfect TrackPoint experience on Linux</a>.</p>]]></content><author><name>João Rocha da Silva</name></author><category term="lenovo" /><category term="trackpoint" /><category term="trackpad" /><category term="synaptics" /><category term="thinkpad" /><category term="p1 gen 2" /><category term="settings" /><category term="ubuntu" /><category term="linux" /><category term="evdev" /><summary type="html"><![CDATA[This post explains how to install the Synaptics driver on Ubuntu 23.04 and how to tune the sensitivity settings on a Lenovo P1 Gen 2.]]></summary></entry><entry><title type="html">iTerm2 (Python API): Automatically starting multiple services in separate panes</title><link href="https://silvae86.github.io/2022/11/19/start-all-services-in-iterm2/" rel="alternate" type="text/html" title="iTerm2 (Python API): Automatically starting multiple services in separate panes" /><published>2022-11-19T13:04:00+00:00</published><updated>2022-11-19T13:04:00+00:00</updated><id>https://silvae86.github.io/2022/11/19/start-all-services-in-iterm2</id><content type="html" xml:base="https://silvae86.github.io/2022/11/19/start-all-services-in-iterm2/"><![CDATA[<p>There are awesome process monitors like <a href="https://pm2.keymetrics.io/">PM2</a> to help manage multiple services, but
sometimes you need to boot up multiple
fully-dockerized workflows and place them in separate panes for easy log monitoring. iTerm2 has a great Python API to
help automatize these repetitive tasks.</p>

<p>Unfortunately, most examples I found on the web using the iTerm2 Python
API started everything in parallel. I needed to actually wait for each server to print its “ready/listening” message
before moving on to the next one. This rules out port clash problems, synchronisation conflicts, etc. Also, when running
on a laptop, sometimes there is not enough CPU power to boot up everything simultaneously.</p>

<p>Here is my example script for starting multiple services in iTerm2, each on their
separate pane. Each one will wait for a completion message before opening the next pane. I separated the logic into a neat <code class="language-plaintext highlighter-rouge">open_pane_and_start_service</code> function that you can call multiple times for starting up all the services you need.</p>

<h2 id="where-to-put-the-script">Where to put the script</h2>

<p>To use it, save it with a <code class="language-plaintext highlighter-rouge">.py</code> extension in the iTerm2 scripts folder.</p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-11-20-start-all-services-in-iterm2/reveal-scripts-folder.png" class="imglightbox" data-description="Opening the iTerm2 Python Scripts folder" data-title="">
		<img src="/assets/images/post-images/2022-11-20-start-all-services-in-iterm2/reveal-scripts-folder.png" alt="" />	
	</a><br />
	<em>Opening the iTerm2 Python Scripts folder</em></p>

<p>The script will then appear in the list of available scripts:</p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-11-20-start-all-services-in-iterm2/run-script-option.png" class="imglightbox" data-description="Script visible in the list of iTerm scripts" data-title="">
		<img src="/assets/images/post-images/2022-11-20-start-all-services-in-iterm2/run-script-option.png" alt="" />	
	</a><br />
	<em>Script visible in the list of iTerm scripts</em></p>

<h2 id="what-it-does-in-detail">What it does, in detail</h2>

<p>The script will:</p>

<ol>
  <li>Open a new pane at a folder that you specify
    <ol>
      <li>Play with <code class="language-plaintext highlighter-rouge">Vertical=True</code> or  <code class="language-plaintext highlighter-rouge">Vertical=False</code> to achieve the layout you need</li>
      <li>Provide an optional <code class="language-plaintext highlighter-rouge">pane_to_split</code> value to specify a previous pane to split from</li>
    </ol>
  </li>
  <li>Run a command</li>
  <li>Wait for a certain line to appear on the screen (typically <code class="language-plaintext highlighter-rouge">Listening at...</code> or similar)</li>
  <li>Move on to the next service.</li>
</ol>

<p>Personally, I use this with multiple <a href="https://mutagen.io/documentation/introduction/installation">mutagen</a> sessions,
which sometimes can take 10+ minutes to perform a full file synchronization first thing in the morning.</p>

<h2 id="the-code">The code</h2>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
</pre></td><td class="rouge-code"><pre><span class="c1">#!/usr/bin/env python3.7
</span><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="kn">import</span> <span class="nn">iterm2</span>


<span class="k">async</span> <span class="k">def</span> <span class="nf">open_pane_and_start_service</span><span class="p">(</span>
        <span class="n">window</span><span class="p">,</span>
        <span class="n">fs_path</span><span class="p">,</span>
        <span class="n">command</span><span class="p">,</span>
        <span class="n">string_to_detect_to_finish</span><span class="p">,</span>
        <span class="n">pane_to_split</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span>
        <span class="n">vertical</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span>
<span class="p">):</span>
    <span class="n">tab</span> <span class="o">=</span> <span class="n">window</span><span class="p">.</span><span class="n">current_tab</span>
    <span class="n">session</span> <span class="o">=</span> <span class="n">tab</span><span class="p">.</span><span class="n">current_session</span>
    <span class="n">pane</span> <span class="o">=</span> <span class="n">session</span>

    <span class="k">if</span> <span class="n">pane_to_split</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
        <span class="n">pane</span> <span class="o">=</span> <span class="k">await</span> <span class="n">session</span><span class="p">.</span><span class="n">async_split_pane</span><span class="p">(</span><span class="n">vertical</span><span class="p">)</span>
        <span class="k">await</span> <span class="n">pane</span><span class="p">.</span><span class="n">async_activate</span><span class="p">()</span>

    <span class="k">async</span> <span class="k">def</span> <span class="nf">run_fs</span><span class="p">():</span>
        <span class="k">await</span> <span class="n">pane</span><span class="p">.</span><span class="n">async_send_text</span><span class="p">(</span><span class="s">'cd '</span> <span class="o">+</span> <span class="n">fs_path</span> <span class="o">+</span> <span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
        <span class="k">await</span> <span class="n">pane</span><span class="p">.</span><span class="n">async_send_text</span><span class="p">(</span><span class="n">command</span> <span class="o">+</span> <span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>

        <span class="n">finished</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="k">async</span> <span class="k">with</span> <span class="n">pane</span><span class="p">.</span><span class="n">get_screen_streamer</span><span class="p">()</span> <span class="k">as</span> <span class="n">streamer</span><span class="p">:</span>
            <span class="k">while</span> <span class="ow">not</span> <span class="n">finished</span><span class="p">:</span>
                <span class="n">stringified_string_contents</span> <span class="o">=</span> <span class="s">""</span>
                <span class="n">screen_output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">streamer</span><span class="p">.</span><span class="n">async_get</span><span class="p">()</span>
                <span class="k">for</span> <span class="n">line_no</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">screen_output</span><span class="p">.</span><span class="n">number_of_lines</span> <span class="o">+</span> <span class="n">screen_output</span><span class="p">.</span><span class="n">number_of_lines_above_screen</span><span class="p">):</span>
                    <span class="n">stringified_string_contents</span> <span class="o">=</span> <span class="n">stringified_string_contents</span> <span class="o">+</span> <span class="n">screen_output</span><span class="p">.</span><span class="n">line</span><span class="p">(</span><span class="n">line_no</span><span class="p">).</span><span class="n">string</span>
                <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">stringified_string_contents</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
                    <span class="n">finished</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="o">=</span><span class="n">string_to_detect_to_finish</span><span class="p">,</span>
                                         <span class="n">string</span><span class="o">=</span><span class="n">stringified_string_contents</span><span class="p">,</span>
                                         <span class="n">flags</span><span class="o">=</span><span class="n">re</span><span class="p">.</span><span class="n">IGNORECASE</span><span class="p">)</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span>
            <span class="k">print</span><span class="p">(</span><span class="s">"Found string [ "</span> <span class="o">+</span> <span class="n">string_to_detect_to_finish</span> <span class="o">+</span> <span class="s">" ] , service is started."</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">pane</span>

    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">get_event_loop</span><span class="p">()</span>
    <span class="n">f_task</span> <span class="o">=</span> <span class="n">loop</span><span class="p">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">run_fs</span><span class="p">())</span>

    <span class="k">await</span> <span class="n">f_task</span>
    <span class="k">return</span> <span class="n">pane</span>


<span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">connection</span><span class="p">):</span>
    <span class="n">app</span> <span class="o">=</span> <span class="k">await</span> <span class="n">iterm2</span><span class="p">.</span><span class="n">async_get_app</span><span class="p">(</span><span class="n">connection</span><span class="p">)</span>
    <span class="n">window</span> <span class="o">=</span> <span class="n">app</span><span class="p">.</span><span class="n">current_terminal_window</span>

    <span class="k">if</span> <span class="n">window</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
        <span class="c1"># Service 1
</span>        <span class="n">service_1</span> <span class="o">=</span> <span class="k">await</span> <span class="n">open_pane_and_start_service</span><span class="p">(</span>
            <span class="n">window</span><span class="p">,</span>
            <span class="s">"~/GitHub/service_1"</span><span class="p">,</span>
            <span class="s">"make start-dev"</span><span class="p">,</span>
            <span class="s">"Watching for changes..."</span><span class="p">,</span>
            <span class="n">vertical</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span>
        <span class="p">)</span>

        <span class="c1"># Service 2
</span>        <span class="n">service_2</span> <span class="o">=</span> <span class="k">await</span> <span class="n">open_pane_and_start_service</span><span class="p">(</span>
            <span class="n">window</span><span class="p">,</span>
            <span class="s">"~/GitHub/service_2"</span><span class="p">,</span>
            <span class="s">"make start-dev"</span><span class="p">,</span>
            <span class="s">"compiled successfully in"</span><span class="p">,</span>
            <span class="n">pane_to_split</span><span class="o">=</span><span class="n">service_1</span><span class="p">,</span>
            <span class="n">vertical</span><span class="o">=</span><span class="bp">True</span>
        <span class="p">)</span>

        <span class="c1"># Service 3
</span>        <span class="n">service_2</span> <span class="o">=</span> <span class="k">await</span> <span class="n">open_pane_and_start_service</span><span class="p">(</span>
            <span class="n">window</span><span class="p">,</span>
            <span class="s">"~/GitHub/service_3"</span><span class="p">,</span>
            <span class="s">"make build &amp;&amp; make start"</span><span class="p">,</span>
            <span class="s">"Listening on"</span><span class="p">,</span>
            <span class="n">vertical</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span>
            <span class="n">pane_to_split</span> <span class="o">=</span> <span class="n">service_2</span>
        <span class="p">)</span>

        <span class="c1"># Service 4
</span>        <span class="n">service_4</span> <span class="o">=</span> <span class="k">await</span> <span class="n">open_pane_and_start_service</span><span class="p">(</span>
            <span class="n">window</span><span class="p">,</span>
            <span class="s">"~/GitHub/service_4"</span><span class="p">,</span>
            <span class="s">"make start"</span><span class="p">,</span>
            <span class="s">"Server started at"</span><span class="p">,</span>
            <span class="n">vertical</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
            <span class="n">pane_to_split</span> <span class="o">=</span> <span class="n">service_3</span>
        <span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="s">"No current window"</span><span class="p">)</span>


<span class="n">iterm2</span><span class="p">.</span><span class="n">run_until_complete</span><span class="p">(</span><span class="n">main</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Now you can start all your microservices while you brew your morning coffee!</p>]]></content><author><name>João Rocha da Silva</name></author><category term="iterm2" /><category term="panes" /><category term="python" /><category term="api" /><category term="development" /><category term="macos" /><category term="microservices" /><category term="terminal" /><summary type="html"><![CDATA[There are awesome process monitors like PM2 to help manage multiple services, but sometimes you need to boot up multiple fully-dockerized workflows and place them in separate panes for easy log monitoring. iTerm2 has a great Python API to help automatize these repetitive tasks.]]></summary></entry><entry><title type="html">Installing clamav (open-source antivirus) for Mac using macports</title><link href="https://silvae86.github.io/2022/09/01/install-clamav-macos-mavericks/" rel="alternate" type="text/html" title="Installing clamav (open-source antivirus) for Mac using macports" /><published>2022-09-01T22:20:00+00:00</published><updated>2022-09-01T22:20:00+00:00</updated><id>https://silvae86.github.io/2022/09/01/install-clamav-macos-mavericks</id><content type="html" xml:base="https://silvae86.github.io/2022/09/01/install-clamav-macos-mavericks/"><![CDATA[<p>Using a vintage Mac requires hardening, such as enabling the Firewall’s Stealth Mode, disabling File Sharing options in the System settings, and installing an anti-virus and being overall mindful of which parts of the web you visit. Here is how to install clamav, a free and open-source alternative for older Apple hardware still stuck on older OS’s. We will be using <a href="https://www.macports.org/">MacPorts</a> for this short tutorial.</p>

<ol>
  <li>
    <p>After installing macports, install the following 2 packages <sup id="fnref:macports-clamav" role="doc-noteref"><a href="#fn:macports-clamav" class="footnote" rel="footnote">1</a></sup>:</p>

    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre> <span class="nb">sudo </span>port <span class="nb">install </span>clamav
 <span class="nb">sudo </span>port <span class="nb">install </span>clamav-server
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
  <li>
    <p>By default, the <code class="language-plaintext highlighter-rouge">freshclam.conf</code> file comes empty <sup id="fnref:cant-parse-clamav-settings" role="doc-noteref"><a href="#fn:cant-parse-clamav-settings" class="footnote" rel="footnote">2</a></sup>. Let’s fix it. Edit the freshclam (process that regularly updates the virus definitions database)</p>

    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre> <span class="nb">sudo </span>vim /opt/local/etc/freshclam.conf
</pre></td></tr></tbody></table></code></pre></div>    </div>

    <p>Add this if the file does not have the <code class="language-plaintext highlighter-rouge">DatabaseMirror</code> line <sup id="fnref:freshclam-fails" role="doc-noteref"><a href="#fn:freshclam-fails" class="footnote" rel="footnote">3</a></sup>:</p>

    <div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre> <span class="n">DatabaseMirror</span> <span class="n">database</span>.<span class="n">clamav</span>.<span class="n">net</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
  <li>Update antivirus definitions database
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre> <span class="nb">sudo </span>freshclam
</pre></td></tr></tbody></table></code></pre></div>    </div>
    <p>It will download a lot of data (&gt; 250MB). Please wait until it finishes.</p>
  </li>
  <li>Enable clamav daemons for continuous database upgrades and background virus scanning
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre> <span class="nb">sudo </span>port load clamav-server
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
</ol>

<p>That being said, I do not recommend using a completely outdated and insecure OS as your main machine on a day-to-day basis. If my 2008 MacBook Pro were my only machine I would be running an up-to-date Linux distro.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:macports-clamav" role="doc-endnote">
      <p>“Installing clamav/clamav-server is very annoying on MacPorts” <a href="https://trac.macports.org/ticket/50570">Link</a> <a href="#fnref:macports-clamav" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:cant-parse-clamav-settings" role="doc-endnote">
      <p>“ERROR: Can’t open or parse the config freshclam.conf” <a href="http://linuxhostingsupport.net/blog/error-cant-open-or-parse-the-config-freshclam-conf">Link</a> <a href="#fnref:cant-parse-clamav-settings" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:freshclam-fails" role="doc-endnote">
      <p>“sudo freshclam -d fails” <a href="https://github.com/Cisco-Talos/clamav/issues/333">Link</a> <a href="#fnref:freshclam-fails" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>João Rocha da Silva</name></author><category term="osx" /><category term="macports" /><category term="antivirus" /><category term="vintage" /><category term="retro" /><summary type="html"><![CDATA[Using a vintage Mac requires hardening, such as enabling the Firewall’s Stealth Mode, disabling File Sharing options in the System settings, and installing an anti-virus and being overall mindful of which parts of the web you visit. Here is how to install clamav, a free and open-source alternative for older Apple hardware still stuck on older OS’s. We will be using MacPorts for this short tutorial.]]></summary></entry><entry><title type="html">Homebrew: A list of useful casks for NodeJS developers</title><link href="https://silvae86.github.io/2022/07/30/list-of-homebrew-casks/" rel="alternate" type="text/html" title="Homebrew: A list of useful casks for NodeJS developers" /><published>2022-07-30T13:40:00+00:00</published><updated>2022-07-30T13:40:00+00:00</updated><id>https://silvae86.github.io/2022/07/30/list-of-homebrew-casks</id><content type="html" xml:base="https://silvae86.github.io/2022/07/30/list-of-homebrew-casks/"><![CDATA[<p>All the casks I usually like to install on a new developer machine.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
</pre></td><td class="rouge-code"><pre><span class="c">#!/usr/bin/env bash</span>



/bin/bash <span class="nt">-c</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">cat </span>install.sh<span class="si">)</span><span class="s2">"</span>

<span class="nb">echo</span> <span class="s1">'eval "$(/opt/homebrew/bin/brew shellenv)"'</span> <span class="o">&gt;&gt;</span> /Users/joaorocha/.zprofile
<span class="nb">eval</span> <span class="s2">"</span><span class="si">$(</span>/opt/homebrew/bin/brew shellenv<span class="si">)</span><span class="s2">"</span>

brew <span class="nb">install</span> <span class="nt">--cask</span> rectangle
brew <span class="nb">install</span> <span class="nt">--cask</span> karabiner-elements
brew <span class="nb">install</span> <span class="nt">--cask</span> iterm2
brew <span class="nb">install</span> <span class="nt">--cask</span> alt-tab 
brew <span class="nb">install</span> <span class="nt">--cask</span> stats
brew <span class="nb">install</span> <span class="nt">--cask</span> webstorm
brew <span class="nb">install</span> <span class="nt">--cask</span> keka
brew <span class="nb">install</span> <span class="nt">--cask</span> chromium
brew <span class="nb">install</span> <span class="nt">--cask</span> firefox
brew <span class="nb">install</span> <span class="nt">--cask</span> signal
brew <span class="nb">install</span> <span class="nt">--cask</span> sqlpro-for-postgres
brew <span class="nb">install</span> <span class="nt">--cask</span> max
brew <span class="nb">install</span> <span class="nt">--cask</span> libreoffice
brew <span class="nb">install</span> <span class="nt">--cask</span> textmate
brew <span class="nb">install</span> <span class="nt">--cask</span> forklift
brew <span class="nb">install</span> <span class="nt">--cask</span> copyclip
brew <span class="nb">install</span> <span class="nt">--cask</span> sensiblesidebuttons
brew <span class="nb">install</span> <span class="nt">--cask</span> htop
brew <span class="nb">install</span> <span class="nt">--cask</span> wget
brew <span class="nb">install</span> <span class="nt">--cask</span> curl
brew <span class="nb">install </span>zsh-completions

<span class="c"># nvm</span>
brew <span class="nb">install </span>nvm

<span class="c"># Docker</span>
brew <span class="nb">install </span>docker
brew <span class="nb">install </span>docker-compose
<span class="nb">mkdir</span> <span class="nt">-p</span> ~/.docker/cli-plugins
<span class="nb">ln</span> <span class="nt">-sfn</span> /opt/homebrew/opt/docker-compose/bin/docker-compose ~/.docker/cli-plugins/docker-compose
</pre></td></tr></tbody></table></code></pre></div></div>]]></content><author><name>João Rocha da Silva</name></author><category term="macos" /><category term="homebrew" /><category term="casks" /><category term="development" /><summary type="html"><![CDATA[All the casks I usually like to install on a new developer machine.]]></summary></entry><entry><title type="html">Homebrew: Fix Command Line Tools installation on macOS Ventura Beta</title><link href="https://silvae86.github.io/2022/07/30/fix-commandline-tools-homebrew-on-macos-ventura/" rel="alternate" type="text/html" title="Homebrew: Fix Command Line Tools installation on macOS Ventura Beta" /><published>2022-07-30T13:36:00+00:00</published><updated>2022-07-30T13:36:00+00:00</updated><id>https://silvae86.github.io/2022/07/30/fix-commandline-tools-homebrew-on-macos-ventura</id><content type="html" xml:base="https://silvae86.github.io/2022/07/30/fix-commandline-tools-homebrew-on-macos-ventura/"><![CDATA[<ol>
  <li>Install XCode Beta 14.0 from Apple’s Developer website.</li>
  <li>Open XCode, set path to command line tools using XCode GUI</li>
</ol>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-07-31-fix-commandline-tools-homebrew-on-macos-ventura/commandline-tools.png" class="imglightbox" data-description="Preferences dialog in XCode 14.0 - Setting up the Command Line Tools home" data-title="Commandline Tools - XCode Preferences">
		<img src="/assets/images/post-images/2022-07-31-fix-commandline-tools-homebrew-on-macos-ventura/commandline-tools.png" alt="Commandline Tools - XCode Preferences" />	
	</a><br />
	<em>Preferences dialog in XCode 14.0 - Setting up the Command Line Tools home</em></p>

<ol>
  <li>Download the homebrew install script, disable check for command line tools</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>curl <span class="nt">-fsSL</span> https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh <span class="o">&gt;</span> install.sh
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Open <code class="language-plaintext highlighter-rouge">install.sh</code>, and replace</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>should_install_command_line_tools<span class="o">()</span> <span class="o">{</span>
	<span class="nt">-----</span>
<span class="o">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>with</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>should_install_command_line_tools<span class="o">()</span> <span class="o">{</span>
	<span class="k">return </span><span class="nb">false</span><span class="p">;</span>
<span class="o">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<ol>
  <li>Homebrew will stop complaining and casks will install now:</li>
</ol>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-07-31-fix-commandline-tools-homebrew-on-macos-ventura/disable-commandline-tools-install.jpg" class="imglightbox" data-description="Modifying the homebrew install script not to install Commandline Tools" data-title="Disable Commandline Tools Install">
		<img src="/assets/images/post-images/2022-07-31-fix-commandline-tools-homebrew-on-macos-ventura/disable-commandline-tools-install.jpg" alt="Disable Commandline Tools Install" />	
	</a><br />
	<em>Modifying the homebrew install script not to install Commandline Tools</em></p>]]></content><author><name>João Rocha da Silva</name></author><category term="macos" /><category term="ventura" /><category term="homebrew" /><category term="xcode" /><category term="command-line" /><category term="tools" /><summary type="html"><![CDATA[Install XCode Beta 14.0 from Apple’s Developer website. Open XCode, set path to command line tools using XCode GUI]]></summary></entry><entry><title type="html">iCloud always full? Organise your AirDrop photos and old hard drives in minutes</title><link href="https://silvae86.github.io/2022/06/30/airdrop-based-media-backup-workflow/" rel="alternate" type="text/html" title="iCloud always full? Organise your AirDrop photos and old hard drives in minutes" /><published>2022-06-30T21:36:00+00:00</published><updated>2022-06-30T21:36:00+00:00</updated><id>https://silvae86.github.io/2022/06/30/airdrop-based-media-backup-workflow</id><content type="html" xml:base="https://silvae86.github.io/2022/06/30/airdrop-based-media-backup-workflow/"><![CDATA[<p><strong>IN CONSTRUCTION!</strong></p>

<p class="float-img-center">
	<a href="
/assets/images/post-images/2022-06-30-automatic-media-organisation-and-backup-from-airdrop/icloud-full.png" class="imglightbox" data-description="iCloud always full! Filming in 4k fills up my iCloud drive in minutes!" data-title="iCloud always bursting at the seams">
		<img src="
/assets/images/post-images/2022-06-30-automatic-media-organisation-and-backup-from-airdrop/icloud-full.png" alt="iCloud always bursting at the seams" />	
	</a><br />
	<em>iCloud always full! Filming in 4k fills up my iCloud drive in minutes!</em></p>

<p>Is the image above a familiar sight? To me it is, and I today I am showing you how to implement an automated workflow
for organising my media. It goes along these lines:</p>

<ol>
  <li>Send my pictures and video to my Mac Mini using AirDrop (Apple’s wireless file transfer that works pretty well).</li>
  <li>The files are saved by AirDrop in a folder in my Mac Mini.</li>
  <li>I should not need to log into the machine to AirDrop files into it. Ideally, this workflow should work with only a
power and network cable connected to the Mini.</li>
  <li>A script detects these new files and, after looking at each file’s EXIF metadata (date taken), sends the files to my
external hard drive in an organised structure.</li>
  <li>The hard drive is organised in a simple folder structure: <code class="language-plaintext highlighter-rouge">&lt;year&gt;/&lt;month&gt;/&lt;day&gt;/&lt;filename&gt;</code>.
    <ul>
      <li>Previous experience has taught me that relying on apps like Aperture (deprecated), Apple Photos (more incompatible
versions than I remember) or Adobe Lightroom is not a good long-term choice. These apps are nice and beautiful but
ultimately make you hostage by holding your photos while they force you to pay/upgrade again and again when and
the old one ceases to work on new OS versions or Apple drops support for your machine.</li>
    </ul>
  </li>
  <li>Duplicates will be detected and deleted from the target folder to save space.</li>
  <li>The script shall run continuously in the background, monitoring the folder for new media files, so that I do not need
to perform any manual actions.</li>
  <li>The script shall use efficient OS mechanisms like <code class="language-plaintext highlighter-rouge">fswatch</code> (which I previously combined with
rsync <a href="/2022/05/20/remote-synchronization-using-fswatch-and-rsync/">here</a> to avoid having to scan the whole folder
continuously. Event handling instead of <em>polling</em>, to minimise resource usage.</li>
  <li>All this has to use free software, because I need money to pay for petrol (€2.30/l in my country at time of writing).</li>
</ol>

<h2 id="selecting-a-media-organisation-software">Selecting a media organisation software</h2>

<p>Like I said, I want to avoid closed-source, large applications by big names like Apple and Adobe, since they tend to
need periodic upgrades. In the case of Adobe, such upgrades will ultimately force you into a subscription. In the case
of Apple, you may be forced to spend money on a more modern computer as your current one is removed from the list of
supported machines. Recently, with the presentation of macOS Ventura we saw
an <a href="https://osxdaily.com/2022/06/22/macos-ventura-compatible-mac-list/">extreme example of this</a>, with my 2018 Mac Mini
barely escaping the axe.</p>

<p>What one needs to understand is that pictures and videos are long-lasting digital assets. The iPhoto library where you
archived your photos 10 years ago will need to be migrated into the most recent Apple Photos version. For that, you may
need to buy an old MacBook, just to pry out the pictures from the library file. And with it, there are no assurances
that the metadata (dates, locations, etc.) will be correctly migrated! We should only rely
on <a href="https://en.wikipedia.org/wiki/Exif">EXIF metadata</a> and a simple structure of folders and files for our long-term
preservation.</p>

<p>After a search on GitHub for <code class="language-plaintext highlighter-rouge">media organization</code>, I selected <a href="https://github.com/ivandokov/phockup">phockup</a> for this
job. It is written in Python and uses <a href="https://exiftool.org/">exiftool</a>, an open-source<sup id="fnref:github-exiftool" role="doc-noteref"><a href="#fn:github-exiftool" class="footnote" rel="footnote">1</a></sup> and
platform-independent tool, to extract EXIF data from the pictures and videos. It is also quite easy to install on a Mac
using <a href="https://brew.sh/">Homebrew</a>.</p>

<h2 id="setting-up-your-mac">Setting up your Mac</h2>

<ol>
  <li>Install <a href="https://brew.sh">Homebrew</a>.</li>
  <li>For macOS Ventura, you may need to install Python from source.
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>brew <span class="nb">install</span> <span class="nt">--build-from-source</span> python@3.9
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
  <li>Install <code class="language-plaintext highlighter-rouge">phockup</code>’s Python <code class="language-plaintext highlighter-rouge">tqdm</code> dependency:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre> pip3 <span class="nb">install </span>tqdm
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
  <li>Install <code class="language-plaintext highlighter-rouge">fswatch</code> :
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre> brew <span class="nb">install </span>fswatch
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
  <li>Install <code class="language-plaintext highlighter-rouge">phockup</code> :
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre> brew tap ivandokov/homebrew-contrib
 brew <span class="nb">install </span>phockup
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
</ol>

<p>Try running <code class="language-plaintext highlighter-rouge">phockup</code>. You should see this:</p>

<p class="float-img-center">
	<a href="
/assets/images/post-images/2022-06-30-automatic-media-organisation-and-backup-from-airdrop/phockup-start.png" class="imglightbox" data-description="Starting up phockup" data-title="Starting up phockup in the Terminal">
		<img src="
/assets/images/post-images/2022-06-30-automatic-media-organisation-and-backup-from-airdrop/phockup-start.png" alt="Starting up phockup in the Terminal" />	
	</a><br />
	<em>Starting up phockup</em></p>

<h2 id="the-synchronisation-script">The synchronisation script</h2>

<p>The basic requirements are:</p>

<ul>
  <li>When new files are detected, call <code class="language-plaintext highlighter-rouge">phockup</code> to organise them to a target folder.</li>
  <li>Only one synchronisation process can execute at any given point in time, to avoid synchronisation conflicts.
    <ul>
      <li>We will be using a simple file <code class="language-plaintext highlighter-rouge">.pid</code> file in the temporary file directory <code class="language-plaintext highlighter-rouge">/tmp</code> for this.</li>
    </ul>
  </li>
  <li>If a synchronisation process crashes, the next run should know that the original process crashed and try again.</li>
</ul>

<p>@see https://superuser.com/questions/28384/what-should-i-do-about-com-apple-quarantine</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
</pre></td><td class="rouge-code"><pre><span class="c">#!/usr/bin/env bash</span>

<span class="c"># NOT WORKING YET!</span>

<span class="c"># Set up two folders to monitor. </span>
<span class="c"># </span>
<span class="c"># - Downloads folder will be monitored for files sent via AirDrop only</span>
<span class="c"># - DropFolder will be monitored for any files </span>
<span class="nv">AIRDROP_FOLDER</span><span class="o">=</span><span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/Downloads"</span>
<span class="nv">SOURCE_FOLDER</span><span class="o">=</span><span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/Pictures/DropFolder"</span>
<span class="nv">TARGET_FOLDER</span><span class="o">=</span><span class="s2">"/Volumes/Backups/Pictures"</span>
<span class="nv">PIDFILE</span><span class="o">=</span><span class="s2">"/tmp/photo_organiser.pid"</span>

<span class="c"># Print lines to Mac's system log Console app</span>
<span class="k">function </span>print_lines_to_system_log<span class="o">()</span> <span class="o">{</span>
  <span class="k">while </span><span class="nv">IFS</span><span class="o">=</span> <span class="nb">read</span> <span class="nt">-r</span> line<span class="p">;</span>
  <span class="k">do
	  </span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$line</span><span class="s2">"</span>
	  syslog <span class="nt">-s</span> <span class="nt">-k</span> Facility com.apple.console <span class="se">\</span>
             Level Notice <span class="se">\</span>
             Sender <span class="s2">"Photo Sync Script by @silvae86"</span><span class="se">\</span>
             Message <span class="s2">"</span><span class="nv">$line</span><span class="s2">"</span>
  <span class="k">done</span> 
<span class="o">}</span>

<span class="nb">echo</span> <span class="s2">"Monitoring files AirDropped into </span><span class="nv">$AIRDROP_FOLDER</span><span class="s2"> and copied into </span><span class="nv">$SOURCE_FOLDER</span><span class="s2">...."</span> | print_lines_to_system_log
<span class="nb">echo</span> <span class="s2">"Media files will be organised into </span><span class="nv">$TARGET_FOLDER</span><span class="s2">."</span> | print_lines_to_system_log

<span class="c"># Run phockup instance on a folder. Will print logs to system log</span>
<span class="c"># Organise all files in the DropFolder. </span>
<span class="c"># Allows manual copying into it, but is useful for organisation of </span>
<span class="c"># old hard drives, for example.</span>
<span class="c"># move_to_source_folder_if_airdropped will move airdropped </span>
<span class="k">function </span>organise_folder<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">FOLDER_TO_ORGANISE</span><span class="o">=</span><span class="nv">$1</span>
  <span class="nb">local </span><span class="nv">PIDFILE</span><span class="o">=</span><span class="nv">$2</span>
  <span class="nb">echo</span> <span class="s2">"Starting organisation of folder </span><span class="nv">$FOLDER_TO_ORGANISE</span><span class="s2">."</span>
  <span class="c"># phockup "$FOLDER_TO_ORGANISE" "$OUTPUT_FOLDER" --verbose | print_lines_to_system_log - &amp;</span>
  <span class="nb">yes</span> <span class="o">&gt;</span> /dev/null &amp;
  <span class="nb">echo</span> <span class="nv">$!</span> <span class="o">&gt;</span> <span class="s2">"</span><span class="nv">$PIDFILE</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"yes running"</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">cat</span> <span class="nv">$PIDFILE</span><span class="si">)</span><span class="s2">"</span>
  <span class="nb">sleep </span>5
  <span class="nb">cat</span> <span class="s2">"</span><span class="nv">$PIDFILE</span><span class="s2">"</span> | xargs <span class="nt">-I</span><span class="o">{}</span> <span class="nb">kill</span> <span class="nt">-9</span> <span class="o">{}</span>
<span class="o">}</span> 

<span class="c"># Moves detected files, only if they were AirDropped but not downloads</span>
<span class="k">function </span>move_to_source_folder_if_airdropped<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">FILE</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
  
  	<span class="c"># Filter files based on their macos extended attributes, including only those sent via AirDrop. </span>
   <span class="c"># This will ignore files downloaded from the internet</span>
   
   <span class="c"># Need to detect a file with a WhereFroms metadata attribute, but the attribute does not include a web address (http://....)</span>
   xattr <span class="nt">-p</span> com.apple.metadata:kMDItemWhereFroms <span class="s2">"</span><span class="nv">$FILE</span><span class="s2">"</span> <span class="o">&gt;</span> /dev/null 2&gt;&amp;1
   <span class="nv">AIRDROPPED_OR_DOWNLOADED</span><span class="o">=</span><span class="nv">$?</span>
   <span class="k">if</span> <span class="o">((</span> <span class="nv">$AIRDROPPED_OR_DOWNLOADED</span> <span class="o">==</span> 1 <span class="o">))</span><span class="p">;</span> <span class="k">then
	   </span><span class="nb">echo</span> <span class="s2">"File </span><span class="nv">$FILE</span><span class="s2"> does not have WhereFrom metadata at all. Skipping."</span>
	   <span class="k">return </span>0
   <span class="k">fi
   
   </span>xattr <span class="nt">-px</span> com.apple.metadata:kMDItemWhereFroms <span class="s2">"</span><span class="nv">$FILE</span><span class="s2">"</span> | xxd <span class="nt">-r</span> <span class="nt">-p</span> | plutil <span class="nt">-convert</span> xml1 - <span class="nt">-o</span> - | <span class="nb">sed</span> <span class="nt">-n</span> <span class="nt">-E</span> <span class="s1">'s/^.*&lt;string&gt;(.*)&lt;\/string&gt;$/\1/p'</span> | <span class="nb">awk</span> <span class="s1">'/http/{print}'</span> | <span class="nb">grep</span> <span class="nb">.</span> <span class="o">&gt;</span> /dev/null
   <span class="nv">DOWNLOADED</span><span class="o">=</span><span class="nv">$?</span>
      
   <span class="k">if</span> <span class="o">((</span> <span class="nv">$DOWNLOADED</span> <span class="o">==</span> 1 <span class="o">))</span><span class="p">;</span> <span class="k">then
      </span><span class="nb">echo</span> <span class="s2">"Airdropped file </span><span class="nv">$FILE</span><span class="s2"> detected. Moving to </span><span class="nv">$SOURCE_FOLDER</span><span class="s2"> to be organised."</span> | print_lines_to_system_log -
      <span class="c"># mv "$FILE" $SOURCE_FOLDER</span>
   <span class="k">fi</span>
<span class="o">}</span>

<span class="c"># Monitor folder for new files using OS-fired events instead of polling (e.g. macOS's `fswatch`)</span>
<span class="k">function </span>monitor_folder<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">FOLDER_TO_MONITOR</span><span class="o">=</span><span class="nv">$1</span>
  <span class="nb">echo</span> <span class="s2">"Monitoring </span><span class="nv">$FOLDER_TO_MONITOR</span><span class="s2">...to move any Airdropped files into </span><span class="nv">$SOURCE_FOLDER</span><span class="s2">"</span> | print_lines_to_system_log -
  
  fswatch <span class="nt">--recursive</span> <span class="s2">"</span><span class="nv">$FOLDER_TO_MONITOR</span><span class="s2">"</span> | move_to_source_folder_if_airdropped - &amp;
<span class="o">}</span>

<span class="c"># Try to move any folders from the source folder</span>
<span class="k">function </span>scan_and_move<span class="o">()</span> <span class="o">{</span>
	<span class="nb">local </span><span class="nv">FOLDER_TO_SCAN</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
	<span class="c"># find all files recursively, run 'file0 command on each. </span>
	<span class="c"># Run 'awk' to match for mimetypes with video or image</span>
	<span class="c"># Then, pipe only those filenames to a while loop</span>
	<span class="c"># to move each file into the $SOURCE_FOLDER</span>
	
	find <span class="s2">"</span><span class="nv">$FOLDER_TO_SCAN</span><span class="s2">"</span> <span class="nt">-type</span> f <span class="nt">-print0</span> <span class="nt">-exec</span> file <span class="nt">--mime-type</span> <span class="o">{}</span> <span class="se">\+</span> | <span class="nb">awk</span> <span class="nt">-F</span>: <span class="s1">'{if ($2 ~/image|video\//) print $1}'</span> | <span class="k">while </span><span class="nb">read </span>file<span class="p">;</span>
	<span class="k">do
		</span>move_to_source_folder_if_airdropped <span class="s2">"</span><span class="nv">$file</span><span class="s2">"</span>
	<span class="k">done</span>
<span class="o">}</span>

<span class="c"># Remove PID file if phockup process died</span>
<span class="k">function </span>try_to_unlock<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">PIDFILE</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
  <span class="k">if</span> <span class="o">[</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$PIDFILE</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> 
  <span class="k">then
    </span><span class="nv">PID</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat</span> <span class="s2">"</span><span class="nv">$PIDFILE</span><span class="s2">"</span><span class="si">)</span>
	ps <span class="nt">-p</span> <span class="s2">"</span><span class="nv">$PID</span><span class="s2">"</span> | <span class="nb">awk</span> <span class="s2">"/</span><span class="nv">$PID</span><span class="s2">/{print}"</span> | <span class="nb">grep</span> <span class="nb">.</span> <span class="o">&gt;</span> /dev/null 2&amp;&gt;1
	<span class="nv">PROCESS_RUNNING</span><span class="o">=</span><span class="nv">$?</span>
	<span class="nb">echo</span> <span class="s2">"Process running? </span><span class="nv">$PROCESS_RUNNING</span><span class="s2">"</span>
	
    <span class="k">if</span> <span class="o">((</span> <span class="nv">$PROCESS_RUNNING</span> <span class="o">!=</span> 0 <span class="o">))</span><span class="p">;</span> 
	<span class="k">then
		</span><span class="nb">echo</span> <span class="s2">"Process </span><span class="nv">$PID</span><span class="s2"> died. Unlocking so a new session can start..."</span> | print_lines_to_system_log -
        <span class="nb">rm</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$PIDFILE</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">"Deleted pidfile at </span><span class="nv">$PIDFILE</span><span class="s2">. New session can start "</span> | print_lines_to_system_log -
		<span class="k">return </span>0
    <span class="k">else
		</span><span class="nb">echo</span> <span class="s2">"Organiser process still running (PID </span><span class="nv">$PID</span><span class="s2">)..."</span>
      	<span class="k">return </span>1
    <span class="k">fi
  else
	  </span><span class="nb">echo</span> <span class="s2">"no pid file found"</span>
  <span class="k">fi
  return </span>0
<span class="o">}</span>

<span class="c"># Perform initial scan</span>
<span class="nb">echo</span> <span class="s2">"Photo organiser performing initial scan...."</span> | print_lines_to_system_log -
scan_and_move <span class="s2">"</span><span class="nv">$AIRDROP_FOLDER</span><span class="s2">"</span>

<span class="c"># After initial scan, monitor folder for new files</span>
monitor_folder <span class="s2">"</span><span class="nv">$AIRDROP_FOLDER</span><span class="s2">"</span>

<span class="c"># Call organisation script if an organisation script is not running already</span>
<span class="k">while</span> :
<span class="k">do
  </span>try_to_unlock <span class="s2">"</span><span class="nv">$PIDFILE</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> organise_folder <span class="s2">"</span><span class="nv">$SOURCE_FOLDER</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$PIDFILE</span><span class="s2">"</span> &amp;
  <span class="nb">sleep </span>1
<span class="k">done</span> &amp;

<span class="nb">wait</span> 
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="making-the-synchronisation-script-run-in-the-background-and-on-startup">Making the synchronisation script run in the background and on startup</h2>

<p>We will use the init system of macOS for starting our synchronisation script in the background whenever the machine
starts.</p>

<h3 id="building-a-plist-file-to-configure-the-daemon">Building a <em>.plist</em> file to configure the daemon</h3>

<h3 id="adding-the-launchdaemon-to-initctl">Adding the LaunchDaemon to initctl</h3>

<h2 id="testing-it-out">Testing it out</h2>

<p>Kięczkowska” <a href="https://eforensicsmag.com/airdrop-forensics-2-by-kinga-kieczkowska/">Link</a></p>

<p>how?” <a href="https://superuser.com/questions/858210/can-you-show-list-all-extended-attributes-and-how">Link</a></p>

<p>Metadata” <a href="https://scriptingosx.com/2017/08/parse-binary-property-lists-in-finder-metadata/">Link</a></p>

<p>[^sed-ignore-non-matching-lines] “Have sed ignore non-matching lines (
StackOverflow)” <a href="https://stackoverflow.com/questions/1665549/have-sed-ignore-non-matching-lines">Link</a></p>

<p>[^loop-filenames-spaces] “BASH Shell: For Loop File Names With Spaces” <a href="https://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html">Link</a></p>

<p>[^list-graphic-images] “BASH Shell: For Loop File Names With Spaces” <a href="https://stackoverflow.com/a/24879385">Link</a></p>

<p>[^awk-grep-dot] “AWK Return Codes - Grep dot trick” <a href="https://unix.stackexchange.com/questions/308838/awk-exit-code-if-regular-expression-did-not-match">Link</a></p>

<p>month and day.” <a href="https://github.com/ivandokov/phockup">Link</a></p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:github-exiftool" role="doc-endnote">
      <p>“Exiftool on GitHub” <a href="https://github.com/exiftool/exiftool">Link</a> <a href="#fnref:github-exiftool" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>João Rocha da Silva</name></author><category term="mac" /><category term="storage" /><category term="icloud" /><category term="full" /><category term="media" /><category term="organisation" /><category term="open" /><category term="source" /><category term="automatic" /><category term="duplicates" /><category term="videos" /><category term="pictures" /><summary type="html"><![CDATA[IN CONSTRUCTION!]]></summary></entry><entry><title type="html">OWC Thunderbay 4 Mini review: 20TB, RAID, Thunderbolt 3 for less than €800</title><link href="https://silvae86.github.io/2022/06/26/thunderbay-mini-4-review-10TB/" rel="alternate" type="text/html" title="OWC Thunderbay 4 Mini review: 20TB, RAID, Thunderbolt 3 for less than €800" /><published>2022-06-26T15:20:00+00:00</published><updated>2022-06-26T15:20:00+00:00</updated><id>https://silvae86.github.io/2022/06/26/thunderbay-mini-4-review-10TB</id><content type="html" xml:base="https://silvae86.github.io/2022/06/26/thunderbay-mini-4-review-10TB/"><![CDATA[<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0129.jpeg" class="imglightbox" data-description="The Thunderbay 4 Mini Packaging" data-title="Thunderbay 4 Packaging">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0129.jpeg" alt="Thunderbay 4 Packaging" />	
	</a><br />
	<em>The Thunderbay 4 Mini Packaging</em></p>

<p>The OWC Thunderbay 4 Mini is a pretty unique value proposition due to its dimensions and flexibility. It sports a tiny footprint, fitting perfectly in a minimalistic desktop, and achieves it by using 2.5’’ hard drives instead of 3.5’’.</p>

<p>Despite being quite expensive for an enclosure at around €350, its Thunderbolt 4 interface makes it very fast, and the 4 SATA bays make it very flexible: you can use SSDs for maximum speed, allowing you to edit video straight off the Thunderbay, or HDDs to cope with some big storage needs. When combined with some hard drive shucking it can become an attractive proposal from a cost/benefit perspective.</p>

<h2 id="design-and-dimensions">Design and dimensions</h2>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0130.jpeg" class="imglightbox" data-description="Thunderbay 4 Mini Design." data-title="Thunderbay 4 Mini Design. It's really tiny.">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0130.jpeg" alt="Thunderbay 4 Mini Design. It's really tiny." />	
	</a><br />
	<em>Thunderbay 4 Mini Design.</em></p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_6779.jpg" class="imglightbox" data-description="Several objects around for size comparison." data-title="Thunderbay 4 Mini Design, compared with other objects.">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_6779.jpg" alt="Thunderbay 4 Mini Design, compared with other objects." />	
	</a><br />
	<em>Several objects around for size comparison.</em></p>

<p>The Thunderbay is an all-aluminium enclosure design. It is appealing and matches the aesthetic that people come to associate with Apple computers. It is minimal and looks great on the desk, with a grill on the front that is somewhat reminiscent of the Classic Mac Pro (“Cheese Grater”). It makes for a timeless industrial look and lets in a sufficient amount of air for cooling the hard drives. The front has a lock, which you need to place in the open position to pull out the front door that covers the hard drives. The key does not come off in the unlocked position, as the front cover would just fall to the front if they did.</p>

<h3 id="the-fan-noise-and-several-solutions">The fan noise (and several solutions)</h3>

<p>The enclosure has a 60mm fan at the back that cools the hard drives inside by pulling air through the front air intake, through the whole enclosure and exhausting it out the back. My unit came with a Noctua fan, which runs at 100% all the time, making it quite noisy at idle. After cracking open the unit, I saw that there are only two wires (one black and one red) that connect the fan to the PCB. I guess there is no RPM reporting, so the PCB just runs it at 12 volts all the time.</p>

<p>It seems not all units come with a Noctua NF-A6x25 FLX fan. Some carry a Sunon one, which seems to be <a href="https://vi-control.net/community/threads/owc-thunderbay-4-with-ssds-turning-the-fan-off.52970/post-5104427">even more noisy</a>. It is disappointing that OWC does not provide any way to regulate the fan speed, as Mac desktops like my Mac Mini tend to be quite silent at idle.</p>

<p>There are several options to solve this:</p>

<ol>
  <li>Unplugging the fan altogether since I am not using SSDs but instead external rotational hard drives, which by design do not need any active cooling.</li>
  <li>Solder a 2 kΩ potentiometer to the red wire that runs from the fan to the PCB. I could then drill a tiny hole in the back of the case for the shaft of the potentiometer and fine tune the speed of the fan as I see fit.</li>
  <li>Replace the fan with a <a href="https://www.blacknoise.com/site/en/products/noiseblocker-it-fans/nb-blacksilentfan-series/60x60x25mm.php">Noiseblocker XL1</a>, which runs at a much lower speed. I would lose overall cooling capacity but would require fewer modifications than the potentiometer option.</li>
</ol>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_6780.jpg" class="imglightbox" data-description="Cracking open the rear hatch to unplug the fan at the back" data-title="Unplugging the fan.">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_6780.jpg" alt="Unplugging the fan." />	
	</a><br />
	<em>Cracking open the rear hatch to unplug the fan at the back</em></p>

<p>Unplugging the fan as easy as removing the two small Torx screws in the back and (gently) pulling the connector from its socket. The enclosure only gets slightly warm to the touch, even after performing several copies of hundreds of thousands of small files—the 2018 Mac Mini i7 by its side is always toasty by comparison! The Thunderbay is also silent when the RAID volume is unmounted (disks spin down), and the seek/write noise level when operating the unit is very reasonable.</p>

<h2 id="shucking-25-hard-drives-for-the-raid-array">Shucking 2.5’’ hard drives for the RAID array</h2>

<p>Hard drive <a href="https://en.wikipedia.org/wiki/Disk_enclosure#Hard_drive_shucking"><em>shucking</em></a> is the process of buying an external (USB) hard drive and cracking open the enclosure to extract the hard drive inside and use it as an internal hard drive. The main advantage is a much lesser cost per drive (internals are approximately 37% more expensive for the same capacity in my particular case) and you get a free enclosure + USB 3 cable. This can be useful for when you finally decide to retire the drives from active use by putting them back in their case.</p>

<p>A very important aspect to take into consideration is that 2.5’’ drives used in external hard drives are 15mm thick, so they will not fit into a laptop, for example. However, they are perfect for the Thunderbay Mini, since the enclosure supports hard drives up to 15mm thick. I initially found this detail in the Thunderbay 4 Mini with Thunderbolt 2 <a href="https://www.owcdigital.com/assets/products/thunderbay-4-mini-thunderbolt-2/thunderbay-4-mini-tb2-user-guide.pdf">manual</a>, and it remains the same for the newer TB3 model.</p>

<p>Drives that can be shucked are rarer and rarer though; you need to do some research before buying your drives.</p>

<h3 id="selecting-a-shuckable-hard-drive">Selecting a shuckable hard drive</h3>

<p>Manufacturers are wise to the fact that people shuck their drives. Practically all external drives in the market are now built with a direct connection between the USB port and the hard drive controller, without a SATA port in between. Western Digital and Toshiba now adopt such a practice, with a notable exception being Seagate. Many Seagate drives still sport a USB-to-SATA adapter board that you can detach, converting the drive back to its ‘internal’ form.</p>

<p>I chose the Seagate Basic Portable 5TB external hard drive (STJL5000400), which inside holds a standard Seagate Barracuda hard drive at 5400rpm. No fancy finishing, simple plastic case (easy to crack open) and, most importantly, uses a SATA-to-USB3 adapter board. I bought 5 of them to have one in reserve, while 4 are in use inside the Thunderbay. While RAID does not require identical disks, I like to have them. It is better to buy them now than be looking for one some years down the road when one drive fails.</p>

<h3 id="shucking-the-seagate-5tb-basic-portable">Shucking the Seagate 5TB Basic Portable</h3>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0119.jpeg" class="imglightbox" data-description="Seagate Basic Portable 5TB external drives" data-title="Seagate 5TB Drives that I will be cracking open">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0119.jpeg" alt="Seagate 5TB Drives that I will be cracking open" />	
	</a><br />
	<em>Seagate Basic Portable 5TB external drives</em></p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0120.jpeg" class="imglightbox" data-description="Stick a spudger in the edge to crack open the enclosure" data-title="Spudger used to open the case">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0120.jpeg" alt="Spudger used to open the case" />	
	</a><br />
	<em>Stick a spudger in the edge to crack open the enclosure</em></p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0121.jpeg" class="imglightbox" data-description="Enclosure open" data-title="Enclosure open">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0121.jpeg" alt="Enclosure open" />	
	</a><br />
	<em>Enclosure open</em></p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0123.jpeg" class="imglightbox" data-description="Hard drive revealed" data-title="Hard drive revealed">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0123.jpeg" alt="Hard drive revealed" />	
	</a><br />
	<em>Hard drive revealed</em></p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0124.jpeg" class="imglightbox" data-description="Hard drive model and part number" data-title="Hard drive model and part number">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0124.jpeg" alt="Hard drive model and part number" />	
	</a><br />
	<em>Hard drive model and part number</em></p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0125.jpeg" class="imglightbox" data-description="Peeling off the sticker" data-title="Peeling off the sticker">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0125.jpeg" alt="Peeling off the sticker" />	
	</a><br />
	<em>Peeling off the sticker</em></p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0126.jpeg" class="imglightbox" data-description="Removing SATA to USB3 adapter board" data-title="Removing SATA to USB3 adapter board">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0126.jpeg" alt="Removing SATA to USB3 adapter board" />	
	</a><br />
	<em>Removing SATA to USB3 adapter board</em></p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0128.jpeg" class="imglightbox" data-description="Removing anti-vibration feet" data-title="Removing anti-vibration feet">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0128.jpeg" alt="Removing anti-vibration feet" />	
	</a><br />
	<em>Removing anti-vibration feet</em></p>

<h3 id="assembling-the-hard-drives-inside-the-thunderbay-enclosure">Assembling the hard drives inside the Thunderbay enclosure</h3>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0131.jpeg" class="imglightbox" data-description="Open up the Thunderbay to expose the 4 hard drive trays..." data-title="Opening the Thunderbay">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0131.jpeg" alt="Opening the Thunderbay" />	
	</a><br />
	<em>Open up the Thunderbay to expose the 4 hard drive trays...</em></p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0138.jpeg" class="imglightbox" data-description="Attach a sliding tray to the drive using the 4 screws already on the drive" data-title="Attach sliding tray to the drive">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0138.jpeg" alt="Attach sliding tray to the drive" />	
	</a><br />
	<em>Attach a sliding tray to the drive using the 4 screws already on the drive</em></p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0135.jpeg" class="imglightbox" data-description="Slide the drive into the slot" data-title="Slide the drive into the slot">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0135.jpeg" alt="Slide the drive into the slot" />	
	</a><br />
	<em>Slide the drive into the slot</em></p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0141.jpeg" class="imglightbox" data-description="Repeat for all 4 drives" data-title="Repeat for all 4 drives">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0141.jpeg" alt="Repeat for all 4 drives" />	
	</a><br />
	<em>Repeat for all 4 drives</em></p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0144.jpeg" class="imglightbox" data-description="Connect and start up the drive. All status LEDs should light up" data-title="Connect and start up the drive. All status LEDs should light up">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/IMG_0144.jpeg" alt="Connect and start up the drive. All status LEDs should light up" />	
	</a><br />
	<em>Connect and start up the drive. All status LEDs should light up</em></p>

<h2 id="choosing-between-raid-10-10-or-01-01">Choosing between RAID 10 (1+0) or 01 (0+1)</h2>

<p>Given that this is a 4-disk configuration, we can go for a number of RAID configurations. The most simple of the nested configurations that do not rely on splitting <em>parity information</em> among different disks are <code class="language-plaintext highlighter-rouge">RAID 10</code> and <code class="language-plaintext highlighter-rouge">RAID 01</code>.</p>

<p>From these there is really only one choice (<code class="language-plaintext highlighter-rouge">RAID 10</code>) since the probability of failure of a RAID 10 array with 4 disks is 33%, while for a <code class="language-plaintext highlighter-rouge">RAID 01</code> that is 66%. Moreover, the probability of failure for a RAID 10 array will tend to 0 as more disks are added, while it will always be at least 50% in a <code class="language-plaintext highlighter-rouge">RAID 01</code> array <sup id="fnref:server-fault-raid-modes" role="doc-noteref"><a href="#fn:server-fault-raid-modes" class="footnote" rel="footnote">1</a></sup>.</p>

<p>In a 4-drive <code class="language-plaintext highlighter-rouge">RAID 10</code> array you first create a <code class="language-plaintext highlighter-rouge">RAID 1</code> array with 2 of the 4 hard drives. You then do the same for the other pair. Then, you build a RAID 0 array from the two <code class="language-plaintext highlighter-rouge">RAID 0</code> arrays. This is also known as <code class="language-plaintext highlighter-rouge">RAID 1+0</code>, mnemonic that tells you the order of the building of the array (first <code class="language-plaintext highlighter-rouge">RAID 1</code> arrays and then you <code class="language-plaintext highlighter-rouge">RAID 0</code> those <code class="language-plaintext highlighter-rouge">RAID 1</code> arrays) <sup id="fnref:raid-10-image" role="doc-noteref"><a href="#fn:raid-10-image" class="footnote" rel="footnote">2</a></sup>.</p>

<p class="float-img-center">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/RAID_10_01.png" class="imglightbox" data-description="Representation of a `RAID 10` array" data-title="`RAID 10` Array">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/RAID_10_01.png" alt="`RAID 10` Array" />	
	</a><br />
	<em>Representation of a `RAID 10` array</em></p>

<h2 id="why-i-chose-apple-disk-utility-instead-of-softraid">Why I chose Apple Disk Utility instead of SoftRAID</h2>

<p>The enclosure includes a license of OWC’s software RAID solution, <a href="https://www.softraid.com/">SoftRAID XT</a>. After some investigation, and even though I paid for the software as a part of the bundle, I decided against using it even before I bought the unit. Instead, I had already opted for Apple’s built-in software RAID instead. Here are the pros and cons of SoftRAID:</p>

<h3 id="-pros">✅ Pros</h3>

<ol>
  <li>Windows + Mac compatibility. You can use your drives on both operating systems, provided SoftRAID is installed.</li>
  <li>Nice UI that is easy to use for everyone</li>
  <li>Drive status monitoring + alerts</li>
</ol>

<h3 id="-cons">❌ Cons</h3>

<ol>
  <li>Should you opt for using SoftRAID, you are stuck with it <em>forever</em>. All data in the Thunderbay will only be readable and writeable with the software installed. It works as a macOS Kernel Extension, meaning that it is flaky as they come, and it working or not will depend on Apple’s whim. Should Apple decide to implement changes to the OS that break SoftRAID’s Kernel Extension, you are left stuck on an older operating system while you wait for OWC to update the software, if at all possible. This has <a href="https://discussions.apple.com/thread/252455756">happened</a> <a href="https://developer.apple.com/forums/thread/666915?page=3">when Apple released macOS Big Sur</a>. For Catalina it was even worse, with similar issues and a <a href="https://eshop.macsales.com/blog/64201-secure-boot-and-sip-guard-your-mac/">nasty fix provided at the time</a> requiring you to disable System Integrity Protection. At least OWC seem to be committed to good customer service, providing not only a <a href="https://www.softraid.com/support/known-issues/">list of known issues</a> with SoftRAID, but also continuing to support the software. Long story short, I worry about using such software to keep valuable data, even if OWC are not to blame.</li>
  <li>Although touted as a selling point of SoftRAID, support for Windows <a href="https://www.reddit.com/r/editors/comments/pyt49v/got_an_owc_thunderbay_4_mini_learned_after_the/">seems flaky</a> and <a href="https://www.reddit.com/r/editors/comments/gb1z8a/looking_at_investing_in_either_a_graid_12tb_2bay/">buggy</a>. For me in particular, this is a non-issue since I only use macOS, but be aware if you plan to use Windows with your Thunderbay.</li>
  <li>The SoftRAID license is single-machine only, meaning that if you own several Macs and need to alternate your Thunderbay between more than one machine, you have to deactivate SoftRAID on the first and activate it on the second machine. Every. Single. Time. Not for me, sorry.</li>
  <li>SoftRAID comes in several flavours (Lite, XT, Pro). The XT version (bundled with the Thunderbay 4 Mini) only allows you to use the software <a href="https://www.softraid.com/">with OWC enclosures</a>. In the event that the enclosure itself fails, I have to buy another one from OWC to get my data out of the hard drives, or spend $ 249 on the Pro version of SoftRAID. By using Apple’s software RAID, it is possible to take out the drives from the dead enclosure, connect them all to a another Mac using USB docks, or to a Hackintosh (which has enough SATA ports, unlike Apple’s modern computers), and get my data out.</li>
  <li>I am not interested in <a href="https://en.wikipedia.org/wiki/Standard_RAID_levels#RAID_5">RAID 5</a> (offered by SoftRAID but not by Apple’s Disk Utility). When compared to RAID 5, RAID 10 offers you more usable storage, but its working principle seem less robust to me. Requiring a minimum of <em>N</em> hard drives, <code class="language-plaintext highlighter-rouge">RAID 5</code> reserves, 1/<em>N</em> of the storage of each drive for parity information. More importantly, it allows a single drive to fail. The problems come when a single drive <strong>does fail</strong>, since the volume rebuild process can take a long time and will put high strain on all the remaining drives. As a rule of thumb, if you have a drive fail, it is reasonable to assume that at least one of the others could be close to failure. Stressing them all for <a href="https://serverfault.com/questions/967930/raid-5-6-rebuild-time-calculation">tens of hours</a> to <a href="https://macperformanceguide.com/blog/2019/20190216_2035-rebuilding-SoftRAID-RAID5.html">rebuild the volume through SoftRAID</a> does not seem healthy, since if any of the other drives fail, you lose all data in all the drives.</li>
  <li>You need SoftRAID to rebuild the volumes in case a drive fails. Using Apple’s software RAID, you can use the command line or the Disk Utility. I like command-line stuff because although ugly, you know it’s going to work and come OOB with every single Mac for years to come.</li>
</ol>

<h2 id="setting-up-raid-10-array-using-apple-disk-utility">Setting up RAID 10 array using Apple Disk Utility</h2>

<p>I will be using Apple Disk Utility. Follow along:</p>

<h3 id="set-up-first-raid-1-array-with-a-pair-of-disks">Set up first RAID 1 array with a pair of disks</h3>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.20.14.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.20.14.png" alt="" />	
	</a></p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.20.21.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.20.21.png" alt="" />	
	</a></p>

<p>Select two of the drives:</p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.20.25.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.20.25.png" alt="" />	
	</a></p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.27.41.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.27.41.png" alt="" />	
	</a></p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.27.47.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.27.47.png" alt="" />	
	</a></p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.27.50.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.27.50.png" alt="" />	
	</a></p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.28.14.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.28.14.png" alt="" />	
	</a></p>

<p>The operation will seem to fail, but if you wait some time you will see the array first appear as grayed out in the list of disks and then go online.</p>

<p>We repeat the process for the remaining two drives:</p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.28.23.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.28.23.png" alt="" />	
	</a></p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.28.28.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.28.28.png" alt="" />	
	</a></p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.28.31.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.28.31.png" alt="" />	
	</a></p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.28.51.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.28.51.png" alt="" />	
	</a></p>

<p>Again we get the same error message, ignore it. After the lights blink in alternating fashion in the Thunderbay, you will see the RAID array come online:</p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.29.15.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.29.15.png" alt="" />	
	</a></p>

<p>Notice the letters <code class="language-plaintext highlighter-rouge">VaultRAID1Slice1</code> in the name of the go from grayed out to black (array online):</p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.29.15.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.29.15.png" alt="" />	
	</a></p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.29.30.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.29.30.png" alt="" />	
	</a></p>

<h3 id="set-up-the-raid-0-with-the-two-nested-raid-1-arrays">Set up the RAID 0 with the two nested RAID 1 arrays</h3>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.29.51.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.29.51.png" alt="" />	
	</a></p>

<p>This time, select RAID 0 instead of RAID 1:</p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.29.55.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.29.55.png" alt="" />	
	</a></p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.29.58.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.29.58.png" alt="" />	
	</a></p>

<p>Choose the final name of your complete array. Here I will be using <code class="language-plaintext highlighter-rouge">Vault</code>, which will represent the complete set of 4 drives in our <code class="language-plaintext highlighter-rouge">RAID 10</code> form.</p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.30.04.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.30.04.png" alt="" />	
	</a></p>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.30.11.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.30.11.png" alt="" />	
	</a></p>

<h3 id="final-result---10tb-in-raid-10">Final Result - 10TB in RAID 10!</h3>

<p class="img-responsive">
	<a href="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.30.28.png" class="imglightbox" data-description="" data-title="">
		<img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/Screenshot 2022-06-22 at 22.30.28.png" alt="" />	
	</a></p>

<h2 id="performance">Performance</h2>

<p>With this configuration (<code class="language-plaintext highlighter-rouge">RAID 10</code>) I got these numbers in BlackMagic Disk Speed Test. Not anywhere near SSD levels, but more than enough for regular media backup. However, it is good to know that I can upgrade to SSDs in the future if I ever need it and prices allow, as the Thunderbolt connection will always allow me to take advantage of their speed.</p>

<p><img src="/assets/images/post-images/2022-06-27-thunderbay-mini-4-review-10TB/blackmagic.png" alt="BlackMagic Disk Speed Test" /></p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:server-fault-raid-modes" role="doc-endnote">
      <p>“Is there a difference between RAID 10 (1+0) and RAID 01 (0+1)?” <a href="https://serverfault.com/questions/145319/is-there-a-difference-between-raid-10-10-and-raid-01-01">Link</a> <a href="#fnref:server-fault-raid-modes" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:raid-10-image" role="doc-endnote">
      <p>“A typical RAID 10 configuration” <a href="https://en.wikipedia.org/wiki/Nested_RAID_levels#/media/File:RAID_10_01.svg">Link</a> <a href="#fnref:raid-10-image" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>João Rocha da Silva</name></author><category term="mac" /><category term="storage" /><category term="owc" /><category term="thunderbay" /><category term="thunderbolt" /><category term="harddrive" /><category term="shucking" /><category term="seagate" /><category term="hardware" /><category term="review" /><summary type="html"><![CDATA[The Thunderbay 4 Mini Packaging]]></summary></entry><entry><title type="html">Automatic synchronization using fswatch and rsync</title><link href="https://silvae86.github.io/2022/05/20/remote-synchronization-using-fswatch-and-rsync/" rel="alternate" type="text/html" title="Automatic synchronization using fswatch and rsync" /><published>2022-05-20T18:00:00+00:00</published><updated>2022-05-20T18:00:00+00:00</updated><id>https://silvae86.github.io/2022/05/20/remote-synchronization-using-fswatch-and-rsync</id><content type="html" xml:base="https://silvae86.github.io/2022/05/20/remote-synchronization-using-fswatch-and-rsync/"><![CDATA[<p>Here is a simple script to automatically synchronize a local directory with a remote one whenever a local change is detected. Combined with a compiler that watches for changes running on the remote server, it can be used to offload heavy compilation tasks to a remote server while using a less powerful computer to edit sources.</p>

<p>This script uses <code class="language-plaintext highlighter-rouge">fswatch</code> to monitor for changes in the local folder and <code class="language-plaintext highlighter-rouge">rsync</code> to perform the actual synchronization to the folder on the remote machine.</p>

<p>It is highly advisable to <a href="/add-ssh-keys-to-remote-server-for-passwordless-login/">configure your SSH keys and hosts</a> to have a completely seamless synchronization experience (i.e. no constant nagging for passwords).</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="c">#!/bin/bash</span>

<span class="c"># Configure these variables to your reality</span>
<span class="nv">REMOTE_HOST</span><span class="o">=</span>macpro
<span class="nv">REMOTE_FOLDER</span><span class="o">=</span>/tmp/silvae86.github.io
<span class="nv">LOCAL_FOLDER</span><span class="o">=</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span>

<span class="c"># initial sync step</span>
rsync <span class="nt">-af</span> <span class="nt">--progress</span> <span class="nb">.</span> <span class="s2">"</span><span class="nv">$REMOTE_HOST</span><span class="s2">:</span><span class="nv">$REMOTE_FOLDER</span><span class="s2">"</span>

<span class="c"># initial check may take some time, later changes may be instant</span>
fswatch <span class="se">\</span>
<span class="nt">--one-per-batch</span> <span class="se">\ </span>
<span class="nt">--recursive</span> <span class="se">\</span>
<span class="nt">--latency</span> 1 <span class="se">\</span>
<span class="nt">--verbose</span> <span class="se">\</span>
<span class="s2">"</span><span class="nv">$LOCAL_FOLDER</span><span class="s2">"</span> | xargs <span class="nt">-I</span><span class="o">{}</span> rsync <span class="nt">-a</span> <span class="nt">--progress</span> <span class="s2">"</span><span class="nv">$LOCAL_FOLDER</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$REMOTE_HOST</span><span class="s2">:</span><span class="nv">$REMOTE_FOLDER</span><span class="s2">"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Explained:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">--one-per-batch</code> → bubble/combine events so we do not ask for a sync every time a single file is changed</li>
  <li><code class="language-plaintext highlighter-rouge">--recursive</code>	→	scan directories and subdirectories</li>
  <li><code class="language-plaintext highlighter-rouge">--latency 1</code>	→ wait 1 second before triggering sync</li>
  <li><code class="language-plaintext highlighter-rouge">"$LOCAL_FOLDER"</code> →source folder</li>
</ul>

<p>Next, a piped command calls <code class="language-plaintext highlighter-rouge">rsync</code> to sync the local dir to the remote one:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">rsync -a --progress "$LOCAL_FOLDER" "$REMOTE_HOST:$REMOTE_FOLDER"</code> → call rsync to synchronize $LOCAL_FOLDER to $REMOTE_FOLDER on $REMOTE_HOST, via SSH.</li>
</ul>]]></content><author><name>João Rocha da Silva</name></author><category term="remote" /><category term="sync" /><category term="rsync" /><category term="fswatch" /><category term="macos" /><category term="command" /><category term="line" /><summary type="html"><![CDATA[Here is a simple script to automatically synchronize a local directory with a remote one whenever a local change is detected. Combined with a compiler that watches for changes running on the remote server, it can be used to offload heavy compilation tasks to a remote server while using a less powerful computer to edit sources.]]></summary></entry><entry><title type="html">Getting Python to install and run on M1 Pro MacBook Pro with macOS Monterey</title><link href="https://silvae86.github.io/2022/04/13/making-pyenv-work-mac-monterey/" rel="alternate" type="text/html" title="Getting Python to install and run on M1 Pro MacBook Pro with macOS Monterey" /><published>2022-04-13T19:10:00+00:00</published><updated>2022-04-13T19:10:00+00:00</updated><id>https://silvae86.github.io/2022/04/13/making-pyenv-work-mac-monterey</id><content type="html" xml:base="https://silvae86.github.io/2022/04/13/making-pyenv-work-mac-monterey/"><![CDATA[<p>In order to get <a href="https://marketplace.visualstudio.com/items?itemName=shakram02.bash-beautify">Bash Beautify</a> working on my Mac, I had to have both the <code class="language-plaintext highlighter-rouge">python2</code> and <code class="language-plaintext highlighter-rouge">python3</code> commands running on my Mac.</p>

<p>After installing pyenv via homebrew:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>brew <span class="nb">install </span>pyenv
</pre></td></tr></tbody></table></code></pre></div></div>

<p>I tried to set my Python versions:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span>pyenv global 3.7.0 2.7.15
pyenv: version <span class="sb">`</span>3.7.0<span class="s1">' not installed
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>Running the installation command produced the following error:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre></td><td class="rouge-code"><pre><span class="nv">$ </span>pyenv <span class="nb">install </span>3.7.0       
python-build: use openssl@1.1 from homebrew
python-build: use readline from homebrew
Downloading Python-3.7.0.tar.xz...
-&gt; https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tar.xz
Installing Python-3.7.0...
python-build: use readline from homebrew
python-build: use zlib from xcode sdk

BUILD FAILED <span class="o">(</span>OS X 12.3.1 using python-build 20180424<span class="o">)</span>

Inspect or clean up the working tree at /var/folders/d1/k0pk6j150dd7xw7qh55n_0bc0000gn/T/python-build.XXXXXXXXX
Results logged to /var/folders/d1/k0pk6j150dd7xw7qh55n_0bc0000gn/T/python-build.XXXXXXXXXX.log

Last 10 log lines:
checking <span class="k">for</span> <span class="nt">--with-cxx-main</span><span class="o">=</span>&lt;compiler&gt;... no
checking <span class="k">for </span>clang++... no
configure:

  By default, distutils will build C++ extension modules with <span class="s2">"clang++"</span><span class="nb">.</span>
  If this is not intended, <span class="k">then </span><span class="nb">set </span>CXX on the configure <span class="nb">command </span>line.
  
checking <span class="k">for </span>the platform triplet based on compiler characteristics... darwin
configure: error: internal configure error <span class="k">for </span>the platform triplet, please file a bug report
make: <span class="k">***</span> No targets specified and no makefile found.  Stop.
</pre></td></tr></tbody></table></code></pre></div></div>

<p>To fix it, I first selected the version I wanted to install with <code class="language-plaintext highlighter-rouge">pyenv install --list</code>. Then, I installed GCC from homebrew, and tried installing again, this time specifying the path to the C++ compiler.</p>

<p>I the following script, replace <code class="language-plaintext highlighter-rouge">gcc-12</code> with a newer version if necessary. Type <code class="language-plaintext highlighter-rouge">cd /opt/homebrew/bin/gcc&lt;Press Tab key&gt;</code> in the Terminal and see what version you have installed.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>brew <span class="nb">install </span>gcc
<span class="nv">CC</span><span class="o">=</span>/opt/homebrew/bin/gcc-12 pyenv <span class="nb">install </span>3.8.12 
<span class="nv">CC</span><span class="o">=</span>/opt/homebrew/bin/gcc-12 pyenv <span class="nb">install </span>2.7.18
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="note-for-macos-ventura-beta">Note for macOS Ventura Beta!</h3>

<p>When running <code class="language-plaintext highlighter-rouge">xcode-select --install</code> you may face this error:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>Can't install the software because it is not
currently available from the Software
Update server
</pre></td></tr></tbody></table></code></pre></div></div>

<p>To fix it, download command line tools from Apple’s <a href="https://developer.apple.com/download/all/">More Downloads</a> page on Apple Developer before running <code class="language-plaintext highlighter-rouge">brew install gcc</code>.</p>

<p>I then set them as the default python versions globally:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>pyenv global 3.9.12 2.7.18
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Done! Bash Beautify works in Visual Studio Code now.</p>]]></content><author><name>João Rocha da Silva</name></author><category term="macintosh" /><category term="python" /><category term="pyenv" /><category term="apple" /><category term="silicon" /><category term="monterey" /><summary type="html"><![CDATA[In order to get Bash Beautify working on my Mac, I had to have both the python2 and python3 commands running on my Mac.]]></summary></entry></feed>