<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Shell Scripting on mm-dev</title>
    <link>https://mm-dev.rocks/tags/shell-scripting/</link>
    <description>Recent content in Shell Scripting on mm-dev</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-gb</language>
    <lastBuildDate>Thu, 06 Mar 2025 12:26:15 +0000</lastBuildDate><atom:link href="https://mm-dev.rocks/tags/shell-scripting/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>End Results and Bonus</title>
      <link>https://mm-dev.rocks/posts/decapitating-macbook/end-results-and-bonus/</link>
      <pubDate>Thu, 06 Mar 2025 12:26:15 +0000</pubDate>
      <guid>https://mm-dev.rocks/posts/decapitating-macbook/end-results-and-bonus/</guid>
      <description>&lt;h4 id=&#34;good&#34;&gt;Good&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;I love that it&amp;rsquo;s silent&lt;/li&gt;
&lt;li&gt;Apple Silicon is as good as I&amp;rsquo;d hoped &amp;mdash; the bang-per-watt is great for my circumstances with solar power etc&lt;/li&gt;
&lt;li&gt;The annoyances with not knowing when it&amp;rsquo;s turned on are largely alleviated with my scripts, as long as I&amp;rsquo;m using it on my LAN (which, realistically I always am)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;bad&#34;&gt;Bad&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;MacOS, when I do have to use it (sorry Mac fans&amp;hellip; I do at least prefer it to Windows)&lt;/li&gt;
&lt;li&gt;The battery does not hold power at all well when turned off (if I turn it off &amp;mdash; yes &lt;em&gt;off&lt;/em&gt;, not asleep &amp;mdash; with a full battery, a few days later it&amp;rsquo;s completely dead and needs to be charged before I can turn it on)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m never going to use this as my main machine, and that was never my plan. I&amp;rsquo;m into minimal Linux with invisible tiling window managers like DWM, and dragging windows around, being forced to watch animations etc won&amp;rsquo;t cut it for my preferences.&lt;/p&gt;
&lt;p&gt;I do know about Asahi Linux but that makes power usage much less special and I&amp;rsquo;m not sure how it plays with the development stack I need for Flutter. More crucially, at time of writing DP Alt Mode is not working, which means monitors plugged in to the USB-C port won&amp;rsquo;t work. Having cut the display off, this scuppers me.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For me this was an investment into being able to responsibly build software for the Apple ecosystem and its users who have different preferences than mine. It&amp;rsquo;s a tool for a specific sub-set of tasks, and under those conditions it does what I need.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;bonus-e-ink-macbook&#34;&gt;Bonus: E-ink Macbook&lt;/h3&gt;
&lt;p&gt;Remember I mentioned that I had a Boox Max Lumi which could function as a monitor for the Macbook? Here it is in full effect.&lt;/p&gt;
&lt;ul class=&#39;gallery single-item&#39;&gt;&lt;li&gt;
      &lt;input type=&#39;checkbox&#39; id=&#39;item_aqEI_1&#39; /&gt;
      &lt;label for=&#39;item_aqEI_1&#39; aria-label=&#39;Expand image&#39; tabindex=&#39;0&#39;&gt;&lt;img src=&#39;https://mm-dev.rocks/images/decapitating-macbook/macbook-eink-max-lumi.jpg&#39; alt=&#39;Macbook using Boox Max Lumi as an E-ink display&#39; /&gt;
        &lt;span&gt;Macbook using Boox Max Lumi as an E-ink display&lt;/span&gt;&lt;/label&gt;
    &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After tweaking some settings in &lt;em&gt;Accessibility&lt;/em&gt;/&lt;em&gt;Display&lt;/em&gt;, MacOS can be made to look half-decent on e-ink.&lt;/p&gt;
&lt;p&gt;All the usual caveats about e-ink apply re refresh rates and ghosting of course, but I could easily see myself working quite happily in Vim on it &lt;em&gt;in some imaginary future dystopia where I&amp;rsquo;m trapped in Apple&amp;rsquo;s walled garden, all that exists, protected by a multi-trillion dollar Reality Distortion Field that they managed to power up just before the bombs dropped&amp;hellip;&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Running Headless: What I&#39;ve Learned</title>
      <link>https://mm-dev.rocks/posts/decapitating-macbook/running-headless-what-ive-learned/</link>
      <pubDate>Thu, 06 Mar 2025 12:26:14 +0000</pubDate>
      <guid>https://mm-dev.rocks/posts/decapitating-macbook/running-headless-what-ive-learned/</guid>
      <description>&lt;h4 id=&#34;the-lid&#34;&gt;The lid&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Removing the lid turns it on (hall sensors in the base are activated by magnets in the lid)&lt;/li&gt;
&lt;li&gt;Replacing the lid leaves the device visible on the network, but it can no longer be &lt;code&gt;SSH&lt;/code&gt;ed into (some form of &amp;lsquo;sleeping&amp;rsquo;)&lt;/li&gt;
&lt;li&gt;During a user-initiated shutdown process (which takes some time, up to 20-30 seconds], replacing the lid re-wakes the machine &amp;mdash; so &lt;em&gt;wait 30 seconds or more before replacing the lid&lt;/em&gt;
&lt;small class=&#34;special&#34;&gt;
As the lid is no longer attached, &amp;lsquo;opening/closing&amp;rsquo; makes no sense so I&amp;rsquo;ll instead use &amp;lsquo;removing/replacing&amp;rsquo;&lt;/small&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;power-button&#34;&gt;Power button&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Holding the power button for &lt;em&gt;5&lt;/em&gt; seconds will begin the &lt;em&gt;shutdown&lt;/em&gt; process, but the machine will still be visible on the network for a few seconds, &lt;em&gt;allow at least 15-20 seconds for shutdown to complete&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Holding the power button for &lt;em&gt;2&lt;/em&gt; seconds will turn the machine &lt;em&gt;on&lt;/em&gt; but it &lt;em&gt;takes maybe 20 seconds&lt;/em&gt; to show up on the network
&lt;small class=&#34;special&#34;&gt;
As mentioned in previous parts of this series, &amp;lsquo;showing on the network&amp;rsquo; is taken as a proxy for &amp;lsquo;machine is turned on&amp;rsquo;&lt;/small&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;from-dead-nolow-power&#34;&gt;From dead (no/low power)&lt;/h4&gt;
&lt;p&gt;A couple of times I&amp;rsquo;ve not used the machine for a week or more and the battery has got very flat &amp;mdash; honestly it&amp;rsquo;s disappointing that it isn&amp;rsquo;t engineered to handle this situation better.&lt;/p&gt;
&lt;p&gt;For a while I struggled to charge it, trying all of the following with no success:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Charging for over 10 mins in first USB-C port&lt;/li&gt;
&lt;li&gt;Charging for over 10 mins in second USB-C port&lt;/li&gt;
&lt;li&gt;Tried powering up several times during the charge periods above&lt;/li&gt;
&lt;li&gt;After each attempt at powering up, waited 2 minutes for sign of life via the Macbook being seen on the LAN
&lt;small class=&#34;special&#34;&gt;
Charging units are dedicated USB PD/QC chargers wired directly to DC 12V/20V feeds, very stable/reliable and with ample amperage&lt;/small&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What eventually got past this problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unplugged all cables&lt;/li&gt;
&lt;li&gt;Plugged one end of a charging cable into the Macbook first (top/corner port)&lt;/li&gt;
&lt;li&gt;THEN plugged the other end of the cable into the USB charger&lt;/li&gt;
&lt;li&gt;30 seconds later I was in (Macbook showed on router as a client and could be &lt;code&gt;ssh&lt;/code&gt;ed into etc)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This could be a red herring eg the order of plugging in cables was not important but instead the battery had accumulated a charge and the final successful attempts just pushed it above some threshold that would allow it to boot. The fact that the response was so quick (30 secs after plugging in the cables in this order the machine was on) makes me think this is unlikely.&lt;/p&gt;
&lt;p&gt;The only other explanation I can think of is that &lt;em&gt;which end of the charging cable is plugged in first makes a difference&lt;/em&gt;, possibly due to something related to USB-C handshake/power negotiations that I&amp;rsquo;m ignorant of. This is a hunch and might make no sense in reality. I leave the info/observation here with very little weight attached to it, it is what it is.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Remote Access</title>
      <link>https://mm-dev.rocks/posts/decapitating-macbook/remote-access/</link>
      <pubDate>Thu, 06 Mar 2025 12:26:13 +0000</pubDate>
      <guid>https://mm-dev.rocks/posts/decapitating-macbook/remote-access/</guid>
      <description>&lt;p&gt;Now I&amp;rsquo;m past the novelty of plugging in monitors this is how I actually use the machine.&lt;/p&gt;
&lt;h3 id=&#34;remote-builds-over-ssh&#34;&gt;Remote builds over SSH&lt;/h3&gt;
&lt;p&gt;&lt;a href = &#34;https://mm-dev.rocks/posts/android-as-a-dev-environment/intro/&#34; title = &#34;Android as a Dev Environment&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;I&amp;rsquo;m working on an Android tablet most of the time&lt;/a&gt;, using Termux, Proot-distro and Termux/X11 to run Debian Bookworm arm64. This setup has shortcomings but I find it very comfortable and manage to get 90% of my work done on it.&lt;/p&gt;
&lt;p&gt;One current problem is that the arm64 version of the Android SDK can&amp;rsquo;t build arm64 APKs for Android. During main development I just build/run the Linux version of whichever app I&amp;rsquo;m working on &amp;mdash; Flutter is multi-platform and suprisingly consistent between platforms. But of course I need to build APKs for Android devices at some point.&lt;/p&gt;
&lt;p&gt;When I just need to quickly build some APKs for a given project, I use something like the following script (this one is for &lt;em&gt;auDAV&lt;/em&gt;, my WebDAV audiobook player app):&lt;/p&gt;

    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Script to be run from Termux/proot-distro&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# - Connects to build machine AIR (host defined in `~/.ssh/config`)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# - Logs in to working directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# - Builds APKs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# - Syncs APKs to local machine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;remote_commands&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;cat &lt;span class=&#34;s&#34;&gt;&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;echo &amp;#34;~~~~~~~ Remote: Source local environment ~~~~~~~&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;source ~/.zprofile
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;source ~/.zshrc
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;echo &amp;#34;~~~~~~~ Remote: Change working directory ~~~~~~~&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;cd ~/development/audav
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;echo &amp;#34;~~~~~~~~~ Remote: Pull latest from git ~~~~~~~~~&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;git pull
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;echo -e &amp;#34;\n~~~~~~~~~~~~~~ Remote: Build APKs ~~~~~~~~~~~~~~&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;flutter build apk --split-per-abi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh AIR &lt;span class=&#34;s2&#34;&gt;&amp;#34;bash --login -c &amp;#39;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;remote_commands&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#39;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;\n~~~~~~~~~~~ Local: Sync APKs to local ~~~~~~~~~~&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;rsync -r --mkpath --progress AIR:development/audav/build/app/outputs/flutter-apk &lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;/000-WORK/audav/build/app/outputs/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p class=&#34;live-code-embed chroma&#34;&gt;&lt;span&gt;From&lt;/span&gt;&lt;a href=&#34;https://codeberg.org/mm-dev/shell-scripts/raw/branch/termux-proot-ubuntu/audav-build-on-mac&#34;&gt;https://codeberg.org/mm-dev/shell-scripts/raw/branch/termux-proot-ubuntu/audav-build-on-mac&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;full-remote-graphical-access-with-nomachine&#34;&gt;Full remote graphical access with NoMachine&lt;/h3&gt;
&lt;ul class=&#39;gallery single-item&#39;&gt;&lt;li&gt;
      &lt;input type=&#39;checkbox&#39; id=&#39;item_t4gj_1&#39; /&gt;
      &lt;label for=&#39;item_t4gj_1&#39; aria-label=&#39;Expand image&#39; tabindex=&#39;0&#39;&gt;&lt;img src=&#39;https://mm-dev.rocks/images/decapitating-macbook/s8-nomachine-macos.jpg&#39; alt=&#39;Remoting into MacOS with S8 tablet, trackball and split keyboard&#39; /&gt;
        &lt;span&gt;Remoting into MacOS with S8 tablet, trackball and split keyboard&lt;/span&gt;&lt;/label&gt;
    &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To build apps for MacOS, iOS and iPadOS I must endure the torturous window management, tedious animations, and terrible file manager of MacOS (and that&amp;rsquo;s before we even get to XCode).&lt;/p&gt;
&lt;p&gt;To ease my suffering I can at least use MacOS via the OLED screen of a Galaxy Tab S8 Ultra, a DEFT PRO trackball and a Ferris Sweep split mechanical keyboard.&lt;/p&gt;
&lt;p&gt;&lt;a href = &#34;https://www.nomachine.com/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;NoMachine&lt;/a&gt; works well for remoting into MacOS and over the LAN it performs well with very little lag. There&amp;rsquo;s client software for Android and arm64 Linux meaning it&amp;rsquo;s quick and easy for me to just pop in to MacOS from my favoured Linux/DWM environment.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Is This Thing On?</title>
      <link>https://mm-dev.rocks/posts/decapitating-macbook/is-this-thing-on/</link>
      <pubDate>Thu, 06 Mar 2025 12:26:11 +0000</pubDate>
      <guid>https://mm-dev.rocks/posts/decapitating-macbook/is-this-thing-on/</guid>
      <description>&lt;p&gt;One of the most difficult problems I&amp;rsquo;ve had with using this machine headless, is &lt;em&gt;knowing whether it&amp;rsquo;s turned on or not&lt;/em&gt;. Seriously.&lt;/p&gt;
&lt;p&gt;If I connect the external monitor (ok, not headless in that case), no image on the screen might mean:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Macbook is not turned on&lt;/li&gt;
&lt;li&gt;The Macbook is asleep&lt;/li&gt;
&lt;li&gt;There is a problem with the monitor or connection (eg bad cable)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If I try to connect over NoMachine or VNC and fail, that might mean:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Macbook is not turned on&lt;/li&gt;
&lt;li&gt;The Macbook is asleep&lt;/li&gt;
&lt;li&gt;The Macbook is not connected to the network&lt;/li&gt;
&lt;li&gt;NoMachine/VNC is not started on the Macbook&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;ve configured something wrongly with NoMachine/VNC&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I really dislike the millions of blinking LEDs designers love to stick on electronic devices. They constantly nag or distract, I think they are bad. But I&amp;rsquo;d kill for one one this Macbook!&lt;/p&gt;
&lt;h3 id=&#34;things-ive-tried&#34;&gt;Things I&amp;rsquo;ve tried&lt;/h3&gt;
&lt;ul class=&#39;gallery single-item&#39;&gt;&lt;li&gt;
      &lt;input type=&#39;checkbox&#39; id=&#39;item_Otin_1&#39; /&gt;
      &lt;label for=&#39;item_Otin_1&#39; aria-label=&#39;Expand image&#39; tabindex=&#39;0&#39;&gt;&lt;img src=&#39;https://mm-dev.rocks/images/decapitating-macbook/macbook-sd-reader-led.jpg&#39; alt=&#39;USB-C SD card reader with LED indicator&#39; /&gt;
        &lt;span&gt;USB-C SD card reader with LED indicator&lt;/span&gt;&lt;/label&gt;
    &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have a small USB-C SD card reader. It has a red LED which lights up when it is receiving power. For a while I used this, but it&amp;rsquo;s impractical. Although it&amp;rsquo;s small, it still sticks out to be breakage risk. A nice bit of leverage on the USB port, just waiting to wrench it off if it catches on something (a bit like the 1st-gen Apple Pencil when it was charging).&lt;/p&gt;
&lt;ul class=&#39;gallery single-item&#39;&gt;&lt;li&gt;
      &lt;input type=&#39;checkbox&#39; id=&#39;item_J3Xs_1&#39; /&gt;
      &lt;label for=&#39;item_J3Xs_1&#39; aria-label=&#39;Expand image&#39; tabindex=&#39;0&#39;&gt;&lt;img src=&#39;https://mm-dev.rocks/images/decapitating-macbook/macbook-capslock-led.jpg&#39; alt=&#39;Capslock button with LED indicator&#39; /&gt;
        &lt;span&gt;Capslock button with LED indicator&lt;/span&gt;&lt;/label&gt;
    &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At some point I noticed that the &lt;code&gt;caps lock&lt;/code&gt; button on the keyboard had an LED which lit when caps was locked. &lt;em&gt;And&lt;/em&gt; (remembering I&amp;rsquo;d usually be using a Bluetooth keyboard and pointing device), the caps lock could be applied to the built-in keyboard without affecting the Bluetooth keyboard. I could just leave caps lock on and the LED would stay lit.&lt;/p&gt;
&lt;p&gt;The showstopper problem with both of the above ideas was that they both only worked when the Macbook was fully booted and ready to rock. I guess the ports and caps lock LED are powered off at other times. Most of my periods of confusion as to whether it was powered on or not were happening when it was half-on, during boot, sleep, shutdown etc.&lt;/p&gt;
&lt;h3 id=&#34;my-current-best-solution&#34;&gt;My current-best solution&lt;/h3&gt;
&lt;p&gt;I noticed that my router, in its list of &amp;lsquo;attached clients&amp;rsquo; was providing relatively quick/up-to-date information about whether the Macbook was connected or not. It seemed to connect to Wi-Fi quite early in the boot process, and disconnect soon after I initiated a shutdown.&lt;/p&gt;
&lt;p&gt;Following on from that, I started &lt;code&gt;ping&lt;/code&gt;ing the Macbook in a terminal window, which I could keep nearby while I worked. This was getting close enough for my purposes.&lt;/p&gt;
&lt;p&gt;The obvious next step was to script it properly. My aim was to get a brief status line telling me if the Macbook was connected or not, and its battery charge level. The following script did the job:&lt;/p&gt;

    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Ping a Mac on the LAN&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# If it&amp;#39;s alive, check its battery level using a seperate script `battery-check` (present on the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Mac)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Return the result with coloured text to indicate off/on status, and rough battery level&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Loop repeatedly, overwriting the results text each time onto the same single line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;ip_to_check&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;192.168.8.123
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;hostname&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;AIR
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;wht&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\033[1;15m&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;red&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\033[1;31m&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;ylw&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\033[1;33m&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;ong&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\033[38;5;208m&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;grn&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\033[1;32m&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;nc&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\033[0m&amp;#39;&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;# No Colour&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;update_battery &lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;# Log in with SSH and call script to get battery charge info.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nv&#34;&gt;battery_check_output&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;ssh AIR &lt;span class=&#34;s2&#34;&gt;&amp;#34;bash --login -c battery-check&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;# `battery-check` returns a string like `battery: 18%`, we want to extract the number.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nv&#34;&gt;percentage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;battery_check_output&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tr -dc &lt;span class=&#34;s1&#34;&gt;&amp;#39;0-9&amp;#39;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[[&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$percentage&lt;/span&gt; -lt &lt;span class=&#34;m&#34;&gt;15&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;battcolor&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$red&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;elif&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[[&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$percentage&lt;/span&gt; -lt &lt;span class=&#34;m&#34;&gt;30&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;battcolor&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$ong&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;elif&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[[&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$percentage&lt;/span&gt; -lt &lt;span class=&#34;m&#34;&gt;80&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;battcolor&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$ylw&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;battcolor&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$grn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Hide cursor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tput civis
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Sometimes (too soon after boot?) the first call fails&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Machete through it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;update_battery
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sleep &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;update_battery
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sleep &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; :
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;# -c 1 ... count 1 (only ping once)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; ping -c &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;ip_to_check&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&amp;gt; /dev/null&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Temporarily go white to indicate battery has just been checked/updated (2 * 5 = 10secs)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[[&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$i&lt;/span&gt; -lt &lt;span class=&#34;m&#34;&gt;2&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nv&#34;&gt;color&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$wht&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nv&#34;&gt;color&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$battcolor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; -ne &lt;span class=&#34;s2&#34;&gt;&amp;#34;  &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;hostname&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;grn&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;    ON     &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;nc&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;color&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;percentage&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;%&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;nc&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;\r&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# i ends up incrementing every 5secs or so&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;((&lt;/span&gt;i++&lt;span class=&#34;o&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Increment and every n counts update battery&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# 5sec * 36 = 180secs = 3mins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[[&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$i&lt;/span&gt; -eq &lt;span class=&#34;m&#34;&gt;36&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nv&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      update_battery
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; -ne &lt;span class=&#34;s2&#34;&gt;&amp;#34;  &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;hostname&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;red&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;   OFF       &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;nc&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;\r&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  sleep &lt;span class=&#34;m&#34;&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p class=&#34;live-code-embed chroma&#34;&gt;&lt;span&gt;From&lt;/span&gt;&lt;a href=&#34;https://codeberg.org/mm-dev/shell-scripts/raw/branch/termux-proot-ubuntu/mac-get-status&#34;&gt;https://codeberg.org/mm-dev/shell-scripts/raw/branch/termux-proot-ubuntu/mac-get-status&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I already had a little script on the Mac to report the battery level (a very simple wrapper around a Mac built-in &lt;code&gt;system_profiler&lt;/code&gt;, just to shorten the output).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;percent&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;system_profiler SPPowerDataType &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep &lt;span class=&#34;s2&#34;&gt;&amp;#34;State of Charge (%)&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; awk &lt;span class=&#34;s1&#34;&gt;&amp;#39;{print $5}&amp;#39;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;battery: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;percent&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;%&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul class=&#39;gallery single-item&#39;&gt;&lt;li&gt;
      &lt;input type=&#39;checkbox&#39; id=&#39;item_M3V4_1&#39; /&gt;
      &lt;label for=&#39;item_M3V4_1&#39; aria-label=&#39;Expand image&#39; tabindex=&#39;0&#39;&gt;&lt;img src=&#39;https://mm-dev.rocks/images/decapitating-macbook/mac-monitor-script.jpg&#39; alt=&#39;Monitoring script in a tiny floating window with close-up&#39; /&gt;
        &lt;span&gt;Monitoring script in a tiny floating window with close-up&lt;/span&gt;&lt;/label&gt;
    &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I wanted to be able to float the &lt;code&gt;mac-get-status&lt;/code&gt; script in a tiny window.&lt;/p&gt;
&lt;p&gt;This next script opens up a terminal in a new window, gives it a specific title &lt;code&gt;macmonitor&lt;/code&gt;, and runs &lt;code&gt;mac-get-status&lt;/code&gt;:&lt;/p&gt;

    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$TERMINAL&lt;/span&gt; --title macmonitor -e &lt;span class=&#34;s2&#34;&gt;&amp;#34;mac-get-status&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p class=&#34;live-code-embed chroma&#34;&gt;&lt;span&gt;From&lt;/span&gt;&lt;a href=&#34;https://codeberg.org/mm-dev/shell-scripts/raw/branch/termux-proot-ubuntu/mac-monitor&#34;&gt;https://codeberg.org/mm-dev/shell-scripts/raw/branch/termux-proot-ubuntu/mac-monitor&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then I tell my window manager (DWM) to apply a special rule to windows with that specific title. This is how it&amp;rsquo;s done in DWM but other window managers may have ways of achieving the same thing.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Rule&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;rules&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// If an Xfce4-terminal window has title &amp;#39;macmonitor&amp;#39;:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;c1&#34;&gt;// - Make it float
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;c1&#34;&gt;// - Make its dimensions 150px x 22px
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Xfce4-terminal&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;macmonitor&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;150&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;22&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// other rules...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>Building Apps and Getting Notified</title>
      <link>https://mm-dev.rocks/posts/my-fdroid-build-setup/building-apps-and-getting-notified/</link>
      <pubDate>Tue, 30 Jul 2024 17:11:49 +0100</pubDate>
      <guid>https://mm-dev.rocks/posts/my-fdroid-build-setup/building-apps-and-getting-notified/</guid>
      <description>&lt;p&gt;If you&amp;rsquo;ve followed along with all of the articles in this series you&amp;rsquo;ll now have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A complete F-Droid build environment set up in a container&lt;/li&gt;
&lt;li&gt;A Telegram bot raring to go&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now we&amp;rsquo;ll put it all together, add some bash scripts to make everything easy to work with, and see how to get notified in Telegram when the local F-Droid build process has finished.&lt;/p&gt;
&lt;p&gt;We use a couple of shell scripts to facilitate this, they&amp;rsquo;re available &lt;a href = &#34;https://codeberg.org/mm-dev/fdroid-shell-scripts&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;here in my Codeberg repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll refer to the Alpine container as the host as all of these instructions are based on that environment, and from that context the F-Droid docker container will be the guest.&lt;/p&gt;
&lt;h3 id=&#34;alpine-container-directory-structure&#34;&gt;Alpine container directory structure&lt;/h3&gt;
&lt;p&gt;So far in our Alpine container we should have a couple of directories which are our local clones of the F-Droid repos &lt;code&gt;fdroiddata&lt;/code&gt; and &lt;code&gt;fdroidserver&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We also need to add a directory where we&amp;rsquo;ll store our shell scripts:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mkdir shell-scripts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So now we should have the following structure (I named my Alpine container &lt;code&gt;fdroid&lt;/code&gt;):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;fdroid:~$ ls -1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;fdroiddata
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;fdroidserver
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;shell-scripts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;the-scripts&#34;&gt;The scripts&lt;/h2&gt;
&lt;p&gt;In &lt;code&gt;~/shell-scripts&lt;/code&gt; we&amp;rsquo;ll have two scripts. Don&amp;rsquo;t forget to make sure they&amp;rsquo;re executable with &lt;code&gt;chmod +x&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;docker-run-fdroidserversh&#34;&gt;docker-run-fdroidserver.sh&lt;/h3&gt;
&lt;p&gt;Run this on the Alpine host to start the container.&lt;/p&gt;
&lt;p&gt;This is really just a &lt;code&gt;docker run&lt;/code&gt; command, based on the example given in the F-Droid docs, then heavily commented for people like me who aren&amp;rsquo;t experts in docker and need to be reminded of the basics.&lt;/p&gt;
&lt;p&gt;Here is the script:&lt;/p&gt;

    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Based on F-Droid&amp;#39;s guide at https://f-droid.org/en/docs/Submitting_to_F-Droid_Quick_Start_Guide/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# The only amendment to this script from the above URL are:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# - Mount an additional volume, the `shell-scripts` directory (parent directory of this script and `fdroid-prepare-env.sh`)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# - Change the entry point to `fdroid-prepare-env.sh`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# - Pass a couple of environment variables, IDs to be used to interact with Telegram so we can get a message pushed when a process finishes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Explanation of command:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# `run`		creates a container from the image (image is given as final argument to the command)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# --rm 		when the container is exited, automatically remove it (the container, not the image)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# -i		`--interactive`, meaning you will be put into a `/bin/bash` (from the later command) session in the container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# -t		`--tty` container output gets sent to a virtual tty, meaning it will be formatted nicely and behave in specific expected ways&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# -u		`--user` so in this case we are running as user &amp;#39;vagrant&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# --entrypoint	overrides the default ENTRYPOINT of the image&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# -v		mount a volume, the arg:arg format mounts a local (1st arg) volume inside the container at (2nd arg)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 		:z at the end means Docker labels the content with a shared content label. Shared volume labels allow all containers to read/write content&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 		:Z at the end means Docker to label the content with a private unshared label. Only the current container can use a private volume&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# -e		environment variable to be passed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# [final arg]	the image from which the container will be created&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Notes on setting up credentials for Telegram bot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Based on tips from https://www.techrepublic.com/article/how-to-safely-store-passwords-linux-server/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# sudo apk add pass gnupg gnupg2 pinentry-tty&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# gpg2 --full-generate-key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# pass init m@mm-dev.rocks&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# pass insert telegrambot/botid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# pass insert telegrambot/chatid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# gpg-connect-agent reloadagent /bye&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo docker run --rm -itu vagrant --entrypoint /home/vagrant/shell-scripts/fdroid-prepare-env.sh &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  -v ~/fdroiddata:/build:z &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  -v ~/fdroidserver:/home/vagrant/fdroidserver:Z &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  -v ~/shell-scripts:/home/vagrant/shell-scripts:Z &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  -e &lt;span class=&#34;nv&#34;&gt;TELEGRAM_BOTID&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;pass telegrambot/botid&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  -e &lt;span class=&#34;nv&#34;&gt;TELEGRAM_CHATID&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;pass telegrambot/chatid&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  registry.gitlab.com/fdroid/fdroidserver:buildserver
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p class=&#34;live-code-embed chroma&#34;&gt;&lt;span&gt;From&lt;/span&gt;&lt;a href=&#34;https://codeberg.org/mm-dev/fdroid-shell-scripts/raw/branch/master/docker-run-fdroidserver.sh&#34;&gt;https://codeberg.org/mm-dev/fdroid-shell-scripts/raw/branch/master/docker-run-fdroidserver.sh&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In addition to the F-Droid original functionality, this version does a few other important things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pass in, as environment variables, some credentials which are required to access a Telegram bot via the Telegram API
&lt;ul&gt;
&lt;li&gt;API token for the bot&lt;/li&gt;
&lt;li&gt;An ID for the chat channel that the message will be delivered to&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Mount our &lt;code&gt;~/shell-scripts&lt;/code&gt; directory in the container so that the &lt;code&gt;fdroid-prepare-env.sh&lt;/code&gt; script is available to it&lt;/li&gt;
&lt;li&gt;Set the &lt;code&gt;fdroid-prepare-env.sh&lt;/code&gt; script as the entry point for the container so that it automatically runs&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;As explained earlier in this series of articles, the Telegram credentials are stored on the host via GPG passwords and passed to the container as environment variables.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This means the credentials will be accessible in the clear to the container user so if this isn&amp;rsquo;t secure enough for your needs you should use another method.&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;fdroid-prepare-envsh&#34;&gt;fdroid-prepare-env.sh&lt;/h3&gt;
&lt;p&gt;This script is run inside the container to finish setting it up.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fdroid-prepare-env.sh&lt;/code&gt; is stored on the Alpine host in the &lt;code&gt;~/shell-scripts&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;But that &lt;code&gt;~/shell-scripts&lt;/code&gt; directory is mounted as a volume inside the container as &lt;code&gt;/home/vagrant/shell-scripts&lt;/code&gt;&lt;br&gt;
&lt;em&gt;&lt;code&gt;vagrant&lt;/code&gt; is the name of the user inside the container&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;So the script is accessible from inside the container&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As it is passed (by the &lt;code&gt;docker-run-fdroidserver.sh&lt;/code&gt; script) to the &lt;code&gt;docker run&lt;/code&gt; command as the entry point, it gets run automatically when the container starts.&lt;/p&gt;
&lt;p&gt;The full script is below:&lt;/p&gt;

    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# #################################&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# F-Droid build tools helper script&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# #################################&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Docker image housekeeping, stuff to be done whenever the F-Droid container is creaated.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Based on F-Droid&amp;#39;s guide at https://f-droid.org/en/docs/Submitting_to_F-Droid_Quick_Start_Guide/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# - Several commands need to be run inside the container every time it starts, meaning constandly having to refer to the above URL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# - Instead, now this script is passed as the entry point to `docker run`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# - Alongside the F-Droid recommendations, export a function `send_tg_alert`, which allows a Telegram message to be sent when a command has completed execution&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Bring in system-wide settings and add some environment variables&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# #################################&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;. /etc/profile
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;PATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$fdroidserver&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PATH&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;PYTHONPATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$fdroidserver&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;JAVA_HOME&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;java -XshowSettings:properties -version 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &amp;gt; /dev/null &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep &lt;span class=&#34;s1&#34;&gt;&amp;#39;java.home&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; awk -F&lt;span class=&#34;s1&#34;&gt;&amp;#39;=&amp;#39;&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;{print $2}&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; tr -d &lt;span class=&#34;s1&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Create the message text to be sent to Telegram&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# #################################&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# - The message contains the shell command (from history) last entered (ie the command that was run and we are informing the recipient about)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# - Use MarkdownV2 syntax, see notes at https://core.telegram.org/bots/api#markdownv2-style&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# - Use heredoc style to create the message and allow sending of newlines to survive the following pipeline:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#     bash | curl (GET, urlencode) | Telegram API&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; get_msg_text &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;c1&#34;&gt;# Enable history, as it&amp;#39;s not usually available from within bash scripts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nb&#34;&gt;set&lt;/span&gt; -o &lt;span class=&#34;nb&#34;&gt;history&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	cat &lt;span class=&#34;s&#34;&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;\`\`\`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;# F\\-Droid command finished at:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;# $(date)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;# Command as entered:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;\$ $(history | cut -c 8-)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;\`\`\`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Send a Telegram message via a bot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# #################################&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; send_tg_alert &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;c1&#34;&gt;# Use heredoc style to create $MSG_TXT and allow sending of newlines to survive:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;c1&#34;&gt;# bash | curl (GET, urlencode) | Telegram API&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nv&#34;&gt;MSG_TXT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;get_msg_text&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$MSG_TXT&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;c1&#34;&gt;# $TELEGRAM_CHATID and $TELEGRAM_BOTID should already exist in this container as environment variables,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;c1&#34;&gt;# having been passed in via the `docker run` command&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	curl &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;		--get &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;		--data-urlencode &lt;span class=&#34;s2&#34;&gt;&amp;#34;chat_id=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;TELEGRAM_CHATID&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;		--data-urlencode &lt;span class=&#34;s2&#34;&gt;&amp;#34;parse_mode=MarkdownV2&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;		--data-urlencode &lt;span class=&#34;s2&#34;&gt;&amp;#34;text=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;MSG_TXT&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;		https://api.telegram.org/bot&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;TELEGRAM_BOTID&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;/sendMessage
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Make functions available to the container and shell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# #################################&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# If functions calls other functions, all functions in the chain need to be exported&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; -f send_tg_alert
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; -f get_msg_text
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Finish up...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# #################################&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Show some helpful reminders&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cat &lt;span class=&#34;s&#34;&gt;&amp;lt;&amp;lt; EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;Example commands:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;fdroid readmeta
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;fdroid rewritemeta com.example
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;fdroid checkupdates --allow-dirty com.example
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;fdroid lint com.example
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;fdroid build com.example
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;To send a Telegram message after the command has completed, append &amp;#39;; send_tg_alert&amp;#39;, eg:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;fdroid build com.example ; send_tg_alert
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Enter the directory which is most likely to be useful when the F-Droid commands are run&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; /build
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# By default, the container would exit at the end of this ENTRYPOINT script --- start a shell instead.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/bin/bash &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$@&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;p class=&#34;live-code-embed chroma&#34;&gt;&lt;span&gt;From&lt;/span&gt;&lt;a href=&#34;https://codeberg.org/mm-dev/fdroid-shell-scripts/raw/branch/master/fdroid-prepare-env.sh&#34;&gt;https://codeberg.org/mm-dev/fdroid-shell-scripts/raw/branch/master/fdroid-prepare-env.sh&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The effects of running the script in the container are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sets up some environment variables as per F-Droid&amp;rsquo;s instructions&lt;/li&gt;
&lt;li&gt;Creates and exports our &lt;code&gt;send_tg_alert&lt;/code&gt; function which can be called to send a message to a Telegram bot when a shell command has finished&lt;/li&gt;
&lt;li&gt;The notification message sent by the function will include the command which was run/finished&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Telegram messaging is useful because the F-Droid build command can take a long time to complete. The function is just appended as an extra command eg:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Build the &amp;#39;com.example.appname&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# then send a Telegram message&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;fdroid build com.example.appname &lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; send_tg_alert
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The F-Droid command will run, and when it ends a Telegram message will be sent to the specified channel, saying something like:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;# F-Droid command finished at:&lt;br&gt;
# Mon Jul 29 17:34:42 UTC 2024&lt;/p&gt;
&lt;p&gt;# Command as entered:&lt;br&gt;
$ fdroid build com.example.appname ; send_tg_alert&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;the-f-droid-build-commands&#34;&gt;The F-Droid build commands&lt;/h2&gt;
&lt;p&gt;Now from inside the docker container (the entry point script should have put us inside the &lt;code&gt;/build&lt;/code&gt; directory already) we can run the fdroid commands.&lt;/p&gt;
&lt;p&gt;You can run &lt;code&gt;fdroid --help&lt;/code&gt; to get a list of subcommands such as:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Build a package from source&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;fdroid build
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Read all the metadata files and exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;fdroid readmeta
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Rewrite all the metadata files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;fdroid rewritemeta
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Warn about possible errors in the metadata&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;fdroid lint
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For each command you can append &lt;code&gt;--help&lt;/code&gt; for more details, eg &lt;code&gt;fdroid build --help&lt;/code&gt;, but check the &lt;a href = &#34;https://f-droid.org/en/docs/Building_Applications/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;official documentation&lt;/a&gt; for full details.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Creating a Telegram Bot</title>
      <link>https://mm-dev.rocks/posts/my-fdroid-build-setup/creating-a-telegram-bot/</link>
      <pubDate>Tue, 30 Jul 2024 17:11:44 +0100</pubDate>
      <guid>https://mm-dev.rocks/posts/my-fdroid-build-setup/creating-a-telegram-bot/</guid>
      <description>&lt;h3 id=&#34;create-the-bot&#34;&gt;Create the bot&lt;/h3&gt;
&lt;p&gt;This is done via the Telegram app, so we need to open that up (if you don&amp;rsquo;t have it installed yet you&amp;rsquo;ll need to do that first).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use search (magnifying glass icon) to find the bot called &amp;lsquo;BotFather&amp;rsquo;. This is an official Telegram bot, used for setting up new bots. Tap it to start a chat with it.&lt;/li&gt;
&lt;li&gt;In the chat with &amp;lsquo;BotFather&amp;rsquo;, use the &lt;code&gt;/newbot&lt;/code&gt; command, this is just a matter of typing the command as a message just like you&amp;rsquo;d send a message to a person&lt;br&gt;
&lt;em&gt;The slash is part of the command so make sure you include it&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Enter a name for your bot, this is the name which will be displayed in chats&lt;/li&gt;
&lt;li&gt;Enter a username for the bot, this is for Telegram&amp;rsquo;s use and must end in &amp;lsquo;_bot&amp;rsquo; (the prompts will tell you this)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This creates the bot, then you will receive a message with some info about your new bot. The main thing you need here is the new bot&amp;rsquo;s token for accessing the HTTP API, this will be a long string something like &lt;code&gt;7901112608:AAD9fDpa9kgw3lZ10o6qANkXnB_D2ZvwuPc&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is your BOT TOKEN, keep a note of it as you need it to do stuff with your bot&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;check-that-everything-is-working-so-far&#34;&gt;Check that everything is working so far&amp;hellip;&lt;/h3&gt;
&lt;p&gt;In a browser, visit a special URL including your bot token (hitting it in a browser like this is one simple way of accessing the API).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The format is &lt;code&gt;https://api.telegram.org/bot&amp;lt;BOT_TOKEN&amp;gt;/getMe&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;So using the example ID above &lt;code&gt;https://api.telegram.org/bot7901112608:AAD9fDpa9kgw3lZ10o6qANkXnB_D2ZvwuPc/getMe&lt;/code&gt;&lt;br&gt;
&lt;em&gt;Make sure the &lt;code&gt;bot&lt;/code&gt; part is there just before the token as it&amp;rsquo;s an essential part of the URL&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;You should see a page in JSON format with some basic data about your new bot&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;create-a-channel&#34;&gt;Create a channel&lt;/h3&gt;
&lt;p&gt;To interact with the bot (eg to receive messages from it), you need to create a channel.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In Telegram, tap the pencil icon&lt;/li&gt;
&lt;li&gt;The &amp;lsquo;New Message&amp;rsquo; dialog will appear, tap &amp;lsquo;New Channel&amp;rsquo;&lt;/li&gt;
&lt;li&gt;Give the channel a name (and description if you wish)&lt;/li&gt;
&lt;li&gt;Choose whether it should be public or private&lt;/li&gt;
&lt;li&gt;Choose subscribers &amp;mdash; &lt;em&gt;here you need to add your new bot&lt;/em&gt;. If you can&amp;rsquo;t see it in the list, tap the blank area at the top to search and type part of its name, it should appear.&lt;br&gt;
&lt;em&gt;The list of subscribers can be edited at any time by tapping its icon in Telegram&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now you can use the API again to get the ID for the channel (or &amp;lsquo;chat&amp;rsquo; as it is referred to). The URL is similar to the one you used above to get the token ID, but the last part changes from &lt;code&gt;getMe&lt;/code&gt; to &lt;code&gt;getUpdates&lt;/code&gt;, so &lt;code&gt;https://api.telegram.org/bot&amp;lt;BOT_TOKEN&amp;gt;/getUpdates&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Like before, you will see some JSON data. You are looking for a &lt;code&gt;channel_post.chat.id&lt;/code&gt;, something like &lt;code&gt;-1002179830041&lt;/code&gt; &lt;em&gt;the dash is part of the ID&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is your CHAT ID, keep a note of this too as it will allow your bot to send messages to this chat/channel&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;test-your-bot&#34;&gt;Test your bot&lt;/h3&gt;
&lt;p&gt;Now you have what you need to send automated messages via your bot. It&amp;rsquo;s as simple as:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Send a message from a Telegram bot to a specific chat/channel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl &lt;span class=&#34;s1&#34;&gt;&amp;#39;https://api.telegram.org/bot&amp;lt;BOT_TOKEN&amp;gt;/sendMessage?chat_id=&amp;lt;CHAT_ID&amp;gt;&amp;amp;text=&amp;lt;YOUR_MESSAGE&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>Storing Secret Credentials</title>
      <link>https://mm-dev.rocks/posts/my-fdroid-build-setup/storing-secret-credentials/</link>
      <pubDate>Tue, 30 Jul 2024 17:11:44 +0100</pubDate>
      <guid>https://mm-dev.rocks/posts/my-fdroid-build-setup/storing-secret-credentials/</guid>
      <description>&lt;p&gt;If you&amp;rsquo;ve already set up a Telegram bot and added it to a channel, you only need 2 things to be able to write code that uses that bot:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The BOT TOKEN - when you create a bot on Telegram you are given this&lt;/li&gt;
&lt;li&gt;A CHAT ID - this points to a specific chat/channel on Telegram&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These bits of info are used to send messages via the Telegram API. Anybody who has them can use your bot, so you want them to be secret, meaning you won&amp;rsquo;t want to hard-code them in any scripts.&lt;/p&gt;
&lt;h2 id=&#34;storing-bot-credentials-using-gpg&#34;&gt;Storing bot credentials using GPG&lt;/h2&gt;
&lt;p&gt;&lt;a href = &#34;https://gnupg.org/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;GNU Privacy Guard&lt;/a&gt; is a set of popular command line tools for working with encrypted secrets. We&amp;rsquo;ll use this to manage our Telegram IDs/keys.&lt;/p&gt;
&lt;h3 id=&#34;first-make-sure-all-the-dependencies-are-installed&#34;&gt;First, make sure all the dependencies are installed&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Some distros might use gnupg, some have gnupg2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# some have both&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apk add pass gnupg gnupg2 pinentry-tty
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;set-up-your-user-ready-for-key-storage&#34;&gt;Set up your user ready for key storage&lt;/h3&gt;
&lt;p&gt;Run the following commands and choose your options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The default options are usually fine, but check out &lt;code&gt;man pass&lt;/code&gt; to learn more&lt;/li&gt;
&lt;li&gt;Use any email address you want, it doesn&amp;rsquo;t have to be real, it will be used as an ID to work with your keystore&lt;br&gt;
&lt;em&gt;Keep a note of whatever address you used&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;gpg2 --full-generate-key
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pass init &amp;lt;YOUR_EMAIL&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;store-your-secrets&#34;&gt;Store your secrets&lt;/h3&gt;
&lt;p&gt;Use &lt;code&gt;pass insert&lt;/code&gt; and give it an argument which you&amp;rsquo;ll use like a label for your secrets so you can edit/delete/retrieve them. You can organise them by using slashes so you can create something like a path or namespace for related secrets.&lt;/p&gt;
&lt;p&gt;You will be prompted to enter the secret you want to store, then again to confirm it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# I want to store a couple of IDs related to a Telegram bot,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# so start both with the same `telegrambot/` string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pass insert telegrambot/bottoken
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pass insert telegrambot/chatid
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;retrieve-your-secrets&#34;&gt;Retrieve your secrets&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pass telegrambot/bottoken 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Output eg &amp;gt; t3l3GRAMbotIDiJustEnT3red&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pass telegrambot/chatid 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Output eg &amp;gt; teleGR4MchatIDiJustEnT3red&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# So eg to set environment variables&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# !WARNING ANYONE WHO CAN READ YOUR VARIABLES&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# WILL BE ABLE TO READ THE SECRET!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;TELEGRAM_BOT_ID&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;pass telegrambot/bottoken&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;to-let-go-of-your-cached-password&#34;&gt;To let go of your cached password&lt;/h3&gt;
&lt;p&gt;After you enter your password GPG hangs on to it for a while, which can make testing difficult while you are getting things set up. If you enter the command below it will be cleaned up and you&amp;rsquo;ll be prompted to enter your password again.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;gpg-connect-agent reloadagent /bye
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>Setting Up the LXC Container</title>
      <link>https://mm-dev.rocks/posts/my-fdroid-build-setup/setting-up-the-lxc-container/</link>
      <pubDate>Tue, 30 Jul 2024 17:11:22 +0100</pubDate>
      <guid>https://mm-dev.rocks/posts/my-fdroid-build-setup/setting-up-the-lxc-container/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m using &lt;a href = &#34;https://linuxcontainers.org/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;LXC containers&lt;/a&gt; on &lt;a href = &#34;https://www.proxmox.com/en/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;Proxmox&lt;/a&gt;. If you have a different method of working with containers I&amp;rsquo;m pretty sure you&amp;rsquo;ll be able to use the info here and repurpose it to your preferences.&lt;/p&gt;
&lt;h3 id=&#34;create-an-alpine-container&#34;&gt;Create an Alpine container&lt;/h3&gt;
&lt;p&gt;I won&amp;rsquo;t go into the details of how to do this as it&amp;rsquo;s specific to Proxmox and no different to setting up any other LXC container in Proxmox.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m using &lt;a href = &#34;https://alpinelinux.org/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;Alpine Linux&lt;/a&gt; as the container distro as it&amp;rsquo;s very lightweight and the F-Droid tools don&amp;rsquo;t need anything fancy.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;In terms of resources, it doesn&amp;rsquo;t need much, the following has been more than enough for me so far:&lt;/del&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;del&gt;512MB RAM&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;512MB swap&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;del&gt;8GB disk&lt;/del&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How naive of me, Gradle is much more greedy than I originally thought and I was getting build errors due to low resources. So I set the container up with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;6GB RAM&lt;/li&gt;
&lt;li&gt;1GB swap&lt;/li&gt;
&lt;li&gt;16GB disk&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;set-up-the-container-for-f-droid-builds&#34;&gt;Set up the container for F-Droid builds&lt;/h3&gt;
&lt;p&gt;While logged into my Alpine container, first I set up some basics:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Ensure everything is up to date&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;apk update
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;apk upgrade
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Install and configure git&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;apk add git vim
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git config --global user.name &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;lt;YOUR_NAME&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git config --global user.email &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;lt;YOUR_EMAIL&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh-keygen
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cat .ssh/id_rsa.pub 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# ... then copy key to gitlab SSH keys&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;F-Droid build instructions depend on docker, so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;apk add docker
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Start docker on boot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;rc-update add docker boot
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Start docker now (or reboot so the above command can take effect)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;service docker start
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now get the F-Droid stuff installed:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone --depth&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; git@gitlab.com:&amp;lt;YOUR_ACCOUNT&amp;gt;/fdroiddata.git ~/fdroiddata
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; ~/fdroiddata
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git checkout -b com.example
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cp templates/build-gradle.yml metadata/com.example.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# If (like me) you already created a fork before it&amp;#39;s a little different &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# and you have to do this instead of the above&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# git clone --depth=1 git@gitlab.com:&amp;lt;YOUR_ACCOUNT&amp;gt;/fdroiddata.git ~/fdroiddata&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# cd fdroiddata/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Shallow cloning makes other branches inaccessible, fix that&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# git remote set-branches origin &amp;#39;*&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# git fetch -v --depth=1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# And now the branch can be checked out&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# git checkout com.example&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The container is set up now, later on on we&amp;rsquo;ll get around to how to actually use it&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Vanilla JS</title>
      <link>https://mm-dev.rocks/posts/vanilla-js/</link>
      <pubDate>Fri, 05 Apr 2024 12:18:03 +0100</pubDate>
      <guid>https://mm-dev.rocks/posts/vanilla-js/</guid>
      <description>&lt;h2 id=&#34;toolchain&#34;&gt;Toolchain&lt;/h2&gt;
&lt;p&gt;I enjoy using plain/vanilla JS where possible. For my own projects I try to avoid the sprawl and long build times of NPM.&lt;/p&gt;
&lt;p&gt;I do like to split my code up into separate modules though, and to be able to benefit from bundling/minification etc, for which I use &lt;em&gt;esbuild&lt;/em&gt;. I also want to be able to use &lt;em&gt;SCSS&lt;/em&gt; and &lt;em&gt;JSDoc&lt;/em&gt; for type annotations. So I do allow myself some minimal tooling (the cone and Flake).&lt;/p&gt;
&lt;p&gt;My setup looks like this&amp;hellip;&lt;/p&gt;
&lt;h3 id=&#34;esbuild---download-a-build&#34;&gt;esbuild - &lt;a href = &#34;https://esbuild.github.io/getting-started/#download-a-build&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;Download a build&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;JS bundler&lt;br&gt;
&lt;em&gt;Combine separate modules into a single JS file, minifying/compressing if required&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Tree shaker&lt;br&gt;
&lt;em&gt;Avoid bloat by making sure only those modules/functions which are used get added&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I use esbuild for most JS projects, so I installed a standalone build which can be run from anywhere &amp;mdash; but it can also be installed using npm or other methods.&lt;/p&gt;
&lt;h3 id=&#34;sass---how-to-install-sass&#34;&gt;SASS - &lt;a href = &#34;https://sass-lang.com/install&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;How to Install SASS&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Compiler and bundler&lt;br&gt;
&lt;em&gt;Converts SASS/SCSS files into CSS&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;jsdoc---documentation&#34;&gt;JSDoc - &lt;a href = &#34;https://jsdoc.app/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;Documentation&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Automated documentation creation&lt;br&gt;
&lt;em&gt;HTML docs for your methods, classes and variables will be created based on your code comments&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Type annotations&lt;br&gt;
&lt;em&gt;Comments in your code can tell JSDoc which data types your code expects&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Type annotations you provide (in specially-formatted comments) will be used to automatically create detailed documentation for your project.&lt;/p&gt;
&lt;p&gt;Docs are created as HTML files, with lots of clickable cross-references. The files can be styled based on a templating system and CSS styling.&lt;/p&gt;
&lt;p&gt;Lots of modern editors/IDEs will also use your type annotations to provide type hints and draw attention to potential problems eg if you try to pass an argument of the wrong type you&amp;rsquo;ll get a warning.&lt;/p&gt;
&lt;h3 id=&#34;example-of-project-setup-using-tmuxvim&#34;&gt;Example of Project Setup Using Tmux/Vim&lt;/h3&gt;
&lt;p&gt;I usually write a simple bash script for most projects. I&amp;rsquo;ll run this script when I want to start a session of working on a specific project.&lt;/p&gt;
&lt;p&gt;Below is an example build script (for &lt;a href = &#34;https://mm-dev.rocks/posts/pipe-dream-web-game/&#34; title = &#34;Pipe Dream&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;my &amp;lsquo;Pipe Dream&amp;rsquo; game&lt;/a&gt;) which does the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enters the project directory&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;tmux&lt;/code&gt; to open a few window panes/splits:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;esbuild&lt;/strong&gt; watching for changes to JS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SASS&lt;/strong&gt; watching for changes to CSS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSDoc&lt;/strong&gt; watching the &lt;code&gt;src&lt;/code&gt; directory (in reality I&amp;rsquo;d often skip this and run it manually on-demand, as it can be slow)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;git&lt;/strong&gt; status, pane left open for commits etc&lt;/li&gt;
&lt;li&gt;A simple &lt;strong&gt;Python server&lt;/strong&gt; so I can test the app in a browser&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Opens a &lt;strong&gt;vim&lt;/strong&gt; session with the relevant source files ready for editing&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Open a JS project using esbuild, SASS and vim&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;work_dir&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$HOME&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;/pipe-dream/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;tmux&amp;#39;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    new-window -c &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$work_dir&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;esbuild src/main.js --outfile=www/js/pipe-dream.js --target=es6 --bundle --minify --sourcemap --global-name=PIPEDREAM --format=iife --watch&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    split-window -c &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$work_dir&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;sass --watch src/scss/:www/css/&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    split-window -c &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$work_dir&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;jsdoc src/ -c jsdoc.config&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    split-window -c &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$work_dir&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;git status; bash -i&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    split-window -c &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$work_dir&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;python -m http.server --directory www/&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;-layout even-vertical &lt;span class=&#34;se&#34;&gt;\;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;    new-window -c &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$work_dir&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;vim -S vim.Session&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;vim-configuration&#34;&gt;Vim Configuration&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m one of those Vim people. People say its codebase is bloated but in use it always feels crisp and fast to me. Because of the way you manipulate text objects and leap around the page it somehow feels efficient on low-powered/slow devices, and works particularly well with &lt;a href = &#34;https://mm-dev.rocks/posts/oled-and-eink-4-life/intro/&#34; title = &#34;OLED and E-Ink&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;e-ink devices&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Some people argue that they&amp;rsquo;re faster while using Vim than they are in other code editors. I&amp;rsquo;m not sure if I&amp;rsquo;m faster (I might be), but I&amp;rsquo;m from the school of thought that if your bottleneck when writing code is your typing speed, that might not be a good thing.&lt;/p&gt;
&lt;p&gt;More importantly for me, I like the way Vim makes you think about text, as beautifully described in this classic stackoverflow answer &amp;lsquo;&lt;a href = &#34;https://stackoverflow.com/questions/1218390/what-is-your-most-productive-shortcut-with-vim/1220118#1220118&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;Your problem with Vim is that you don&amp;rsquo;t grok vi&lt;/a&gt;&amp;rsquo;.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t like my coding environment to be too noisy or intrusive, but there are some powerful IDE features available nowadays and I don&amp;rsquo;t want to cut off my nose to spite my face.&lt;/p&gt;
&lt;p&gt;Below is some info on some Vim plugins I use to help with JS development.&lt;/p&gt;
&lt;h3 id=&#34;coc-nvim---code-of-conquer-project-repo&#34;&gt;coc-nvim - &lt;a href = &#34;https://github.com/neoclide/coc.nvim&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;Code of Conquer project repo&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Although the &amp;rsquo;nvim&amp;rsquo; stands for Neovim (the popular Vim fork), COC also works very well in modern versions of normal Vim. This plugin allows VSCode-type IDE features, being based on the &lt;a href = &#34;https://en.wikipedia.org/wiki/Language_Server_Protocol&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;Language Server Protocol&lt;/a&gt; standard.&lt;/p&gt;
&lt;p&gt;Very simply, a language server is a tool which understands a specific language, and can be used by your editor/IDE to analyse and give feedback on your code as you write it. It can do things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Syntax highlighting&lt;/li&gt;
&lt;li&gt;Code completion&lt;/li&gt;
&lt;li&gt;Draw attention to errors and give warnings about code&lt;/li&gt;
&lt;li&gt;Help when refactoring code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;COC isn&amp;rsquo;t a language server itself but it allows you to install and maintain whichever servers you need for the languages you use.&lt;/p&gt;
&lt;p&gt;For example the language server I use for JS is &lt;a href = &#34;https://github.com/neoclide/coc-tsserver&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;coc-tsserver&lt;/a&gt;. The &amp;rsquo;ts&amp;rsquo; stands for TypeScript but it works very well with standard JS too. Along with the benefits mentioned above, this language server allows you to use type-checking, like TypeScript but without the compilation step. It also provides documentation and hints on using standard built-in JS methods.&lt;/p&gt;
&lt;p&gt;You annotate your code by providing special comments in &lt;em&gt;JSDoc&lt;/em&gt; syntax. When these comments/annotations are present, COC and the language server give you lots of hints while writing code, and warn you if you try to provide arguments of the wrong type.&lt;/p&gt;
&lt;p&gt;Here is an example (from the &lt;a href = &#34;https://jsdoc.app/howto-es2015-classes&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;official docs&lt;/a&gt;) showing what the &lt;em&gt;JSDoc&lt;/em&gt; syntax looks like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-JavaScript&#34; data-lang=&#34;JavaScript&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/** Class representing a point. */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Point&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;cm&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     * Create a point.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     * @param {number} x - The x value.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     * @param {number} y - The y value.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;constructor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;y&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;cm&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     * Get the x value.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     * @return {number} The x value.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;getX&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;cm&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     * Get the y value.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     * @return {number} The y value.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;getY&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;cm&#34;&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     * Convert a string containing two comma-separated numbers into a point.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     * @param {string} str - The string containing two comma-separated numbers.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     * @return {Point} A Point object.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;     */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fromString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Sometimes the help is a little noisy though&amp;hellip;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I love the functionality, but for me it&amp;rsquo;s a bit over-eager, eg if I&amp;rsquo;m half-way through typing a word I don&amp;rsquo;t need a popup telling me the word doesn&amp;rsquo;t exist.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;coc-nvim&lt;/em&gt; does also provide a small symbol at the start of each line where it detects an error or wants to warn about something. So I like to leave the symbol visible, but hide the info popups by default. I add a keyboard shortcut so I can show/hide the explanation of the error/warning when I want to.&lt;/p&gt;
&lt;p&gt;To stop the popups:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-JSON&#34; data-lang=&#34;JSON&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// In ~/.vim/coc-settings.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;err&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;diagnostic.enableMessage&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;jump&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;err&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To add the show/hide keyboard shortcuts to Vim:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-VimL&#34; data-lang=&#34;VimL&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;&amp;#34; In ~/.vimrc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;nmap&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;silent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;gh&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Plug&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;coc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;float&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hide&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;nmap&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;silent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;gl&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Plug&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;coc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;diagnostic&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;prettier&#34;&gt;Prettier&lt;/h3&gt;
&lt;p&gt;This &lt;a href = &#34;https://prettier.io/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;opinionated code formatter&lt;/a&gt; formats your code according to some fairly strict rules and has few configurable options (ie it&amp;rsquo;s their way or the highway).&lt;/p&gt;
&lt;p&gt;I think most people find at least some of the rules disagreeable, but the point of this kind of tool is that it does a &amp;lsquo;decent, not perfect&amp;rsquo; job, but provides a standard for formatting and removes the need for discussing, disagreement and wasted time.&lt;/p&gt;
&lt;p&gt;I found it a little annoying at first but then got over myself and prefer now to use it whenever possible.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The Bad Parts</title>
      <link>https://mm-dev.rocks/posts/android-as-a-dev-environment/the-bad-parts/</link>
      <pubDate>Fri, 05 Apr 2024 11:44:43 +0100</pubDate>
      <guid>https://mm-dev.rocks/posts/android-as-a-dev-environment/the-bad-parts/</guid>
      <description>&lt;h3 id=&#34;the-low-memory-killer-daemon---official-docs&#34;&gt;The Low Memory Killer Daemon - &lt;a href = &#34;https://source.android.com/docs/core/perf/lmkd&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;Official Docs&lt;/a&gt;.&lt;/h3&gt;
&lt;p&gt;This feature of modern Android versions kills tasks (/apps) when it thinks they are causing memory-related performance problems. It can feel pretty heavy-handed, with apps abruptly closing at seemingly random intervals.&lt;/p&gt;
&lt;p&gt;In practice this can irritate but I&amp;rsquo;ve never lost any work because of it. The most noticeable occurrence is when &lt;em&gt;Termux:X11&lt;/em&gt; closes &amp;mdash; my Linux desktop suddenly shuts down! Sounds terrible But actually my Vim buffers are constantly auto-saved, my browser tabs are stored&amp;hellip; I just have to restart the session, then &lt;em&gt;Tmux&lt;/em&gt;, then maybe a couple of other items. I&amp;rsquo;m back up and running in 1-2 minutes, and this problem might occur once or twice per week at the most.&lt;/p&gt;
&lt;p&gt;There are various ways to tweak the daemon though, search for terms like &amp;lsquo;disable android oom killer&amp;rsquo;, &amp;rsquo;tweak low memory killer daemon android&amp;rsquo; etc.&lt;/p&gt;
&lt;h3 id=&#34;missing-tools-for-arm-architecture&#34;&gt;Missing Tools for ARM Architecture&lt;/h3&gt;
&lt;p&gt;As the types of CPU in Android devices (usually ARM) are not the same as those used in laptops/desktops (usually X86), the standard desktop versions of software can&amp;rsquo;t be installed and compatible versions must be available (unless you want to compile them yourself).&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t as bad as it sounds as the package manager for whichever distro you use will automatically pull in the correct version for your CPU architecture. Some software might be missing though.&lt;/p&gt;
&lt;p&gt;A couple of specific gaps in the system affect me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Garmin Monkey C&lt;br&gt;
&lt;em&gt;Garmin apps are written in their own language &amp;lsquo;Monkey C&amp;rsquo;, which has to use their own Java-based compiler and device simulator. As far as I can tell this isn&amp;rsquo;t happening on ARM. It was hard enough to get it working on Linux and Vim as they presume Windows/VSCode in their tutorials, so once I achieved that I didn&amp;rsquo;t take it any further.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Flutter cross-compilation&lt;br&gt;
&lt;em&gt;The situation with Flutter is strange. Flutter is used to make multi-platform apps, so the same code can output apps for Linux, Android, Windows etc. I can build a Linux (ARM64) binary on my Android Ubuntu&amp;hellip; and I can build an Android binary on my (normal, X86 desktop) Linux. But I can&amp;rsquo;t build an Android binary from Android. Apparently people were able to do this on older versions of Flutter, so it seems like Google (who control Flutter) intentionally stopped supporting it.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;fingers-crossed&#34;&gt;Fingers crossed!&lt;/h3&gt;
&lt;p&gt;Mine is a niche use-case and I understand that I can&amp;rsquo;t expect organisations to support such a tiny minority as ARM Linux. Apple&amp;rsquo;s M1/M2/M3 ARM chips are doing very well, which is great if you&amp;rsquo;re into that but I don&amp;rsquo;t enjoy their ecosystem.&lt;/p&gt;
&lt;p&gt;So for now I need to hang on to my X86 desktop. But I hope ARM continues to gain recognition as more than a mobile phone CPU and can be used for more development tasks in future.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Software</title>
      <link>https://mm-dev.rocks/posts/android-as-a-dev-environment/software/</link>
      <pubDate>Fri, 05 Apr 2024 11:44:41 +0100</pubDate>
      <guid>https://mm-dev.rocks/posts/android-as-a-dev-environment/software/</guid>
      <description>&lt;p&gt;&lt;em&gt;Although this is all in relation to working on Android, the software I use is generally the same as if I was on a Linux desktop. That&amp;rsquo;s kind of the point, nowadays I can use almost the same environment on Android as I can on my desktop.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;termux&#34;&gt;Termux&lt;/h3&gt;
&lt;p&gt;&lt;a href = &#34;https://termux.dev/en/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;Termux&lt;/a&gt; is a brilliant free terminal app and Linux environment for Android. It has most of the Linux commands I need and I can install &lt;em&gt;git&lt;/em&gt;, &lt;em&gt;Vim&lt;/em&gt;, &lt;em&gt;Tmux&lt;/em&gt;, &lt;em&gt;mutt&lt;/em&gt;, &lt;em&gt;ImageMagick&lt;/em&gt;  and the majority of the terminal software I like to use. For certain jobs such as scripting, SSH sessions and writing, this is all I need.&lt;/p&gt;
&lt;p&gt;Termux can run non-terminal, graphical apps too, but you won&amp;rsquo;t be able to see them. That&amp;rsquo;s what the next app is for.&lt;/p&gt;
&lt;h3 id=&#34;termuxx11&#34;&gt;Termux:X11&lt;/h3&gt;
&lt;p&gt;&lt;a href = &#34;https://github.com/termux/termux-x11&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;This amazingly useful app&lt;/a&gt; describes itself as being in early development, but I&amp;rsquo;ve been daily-driving it for months without significant issues.&lt;/p&gt;
&lt;p&gt;Termux:X11 is an X Server for Termux. This allows you to install graphical Linux apps, as long as they are available for your system architecture (ie AArch64 &amp;mdash; or ARM64 as it&amp;rsquo;s known &amp;mdash; for most modern Android devices).&lt;/p&gt;
&lt;p&gt;This means I can run, directly on my tablet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Desktop &lt;em&gt;Firefox&lt;/em&gt; (so, full developer tools)&lt;/li&gt;
&lt;li&gt;Desktop &lt;em&gt;Chromium&lt;/em&gt; (this was quite hard to get installed and isn&amp;rsquo;t the absolute latest version but is still very useful for development)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Krita&lt;/em&gt;/&lt;em&gt;Inkscape&lt;/em&gt;/&lt;em&gt;GIMP&lt;/em&gt; for image processing&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Thunar&lt;/em&gt; file manager&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Audacity&lt;/em&gt; (using &lt;em&gt;PulseAudio&lt;/em&gt; over the network to pipe the audio up to Android by using the devices own IP address&amp;hellip; I&amp;rsquo;m sure this creates some delay/latency but I&amp;rsquo;ve not noticed it in practice and for my typical usage of minor adjustments to audio files it works well)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;proot-distro&#34;&gt;PRoot Distro&lt;/h3&gt;
&lt;p&gt;This is a &amp;lsquo;container environment manager&amp;rsquo; and allows you to easily install, uninstall, backup and restore Linux distributions, alongside/on-top-of Android, sharing the kernel.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s really simple to use, eg installing Debian Linux is a matter of opening &lt;em&gt;Termux&lt;/em&gt; and entering:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;proot-distro install debian
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then for a shell login:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;proot-distro login debian
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;PRoot Distro &lt;a href = &#34;https://github.com/termux/proot-distro&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;provides a nice choice of distros&lt;/a&gt; including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alpine Linux (edge)&lt;/li&gt;
&lt;li&gt;Arch Linux ARM&lt;/li&gt;
&lt;li&gt;Artix Linux (AArch64 only)&lt;/li&gt;
&lt;li&gt;Debian (stable)&lt;/li&gt;
&lt;li&gt;Fedora 38 (AArch64 only)&lt;/li&gt;
&lt;li&gt;Manjaro (AArch64 only)&lt;/li&gt;
&lt;li&gt;OpenSUSE (Tumbleweed)&lt;/li&gt;
&lt;li&gt;Pardus (yirmibir)&lt;/li&gt;
&lt;li&gt;Ubuntu (23.10)&lt;/li&gt;
&lt;li&gt;Void Linux&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can install as many distros as you want and swap between them easily. Each distro just sits in a directory on the Android device. Using the backup tools the entire system can be bundled up as a single archive file and moved over to another Android device.&lt;/p&gt;
&lt;h2 id=&#34;window-management&#34;&gt;Window Management&lt;/h2&gt;
&lt;p&gt;I believe that the best user interface (actually, interface of any kind) is one that I don&amp;rsquo;t even notice exists. It doesn&amp;rsquo;t shout at me, it doesn&amp;rsquo;t wow me with animations and eye-candy. It allows me to get to the tools I need to get the job done and stay immersed in the task. It is at peace with itself enough to take a back seat and not demand attention. It is, conceptually, transparent.&lt;/p&gt;
&lt;p&gt;A lot of people like the gestural UI featured in &amp;lsquo;Minority Report&amp;rsquo;. I like the film, and the scene with Tom Cruise manipulating the UI is well produced and fun to watch. But as a UI it&amp;rsquo;s terrible! Hugely inefficient with all those big sweeping hand gestures, it&amp;rsquo;s not quick or practical. I don&amp;rsquo;t want to have to perform interpretive dance to use my computer (nor watch my computer doing little performances when I&amp;rsquo;m trying to get stuff done).&lt;/p&gt;
&lt;p&gt;I found similar sentiment in this &lt;a href = &#34;https://daringfireball.net/2024/01/the_vision_pro&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;Daring Fireball article about the Apple Vision Pro&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;To me the Macintosh has always felt more like a place than a thing. Not a place I go physically, but a place my mind goes intellectually. When I’m working or playing and in the flow, it has always felt like MacOS is where I am. I’m in the Mac.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Interruptions — say, the doorbell or my phone ringing — are momentarily disorienting when I’m in the flow on the Mac, because I’m pulled out of that world and into the physical one.&amp;rdquo;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;I am nearly exactly like this too, except I don&amp;rsquo;t want to feel like I&amp;rsquo;m &amp;ldquo;in the Linux&amp;rdquo; (and definitely not &amp;ldquo;in the Mac&amp;rdquo; or &amp;ldquo;in Windows&amp;rdquo;!). I don&amp;rsquo;t want to be aware of that layer of abstraction at all. I don&amp;rsquo;t mind feeling that I&amp;rsquo;m &amp;ldquo;in Vim&amp;rdquo;, but really I want to be &amp;ldquo;in the code&amp;rdquo; that I&amp;rsquo;m editing. I specifically don&amp;rsquo;t want to feel the manicured claw of a corporate behemoth like Apple or Microsoft on my shoulder&amp;hellip; but that&amp;rsquo;s just me.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d rather forget that my OS(/window manager) exists. It&amp;rsquo;s a facilitator, not an end in itself.&lt;/p&gt;
&lt;h3 id=&#34;dwm-tiling-window-manager&#34;&gt;DWM: Tiling Window Manager&lt;/h3&gt;
&lt;p&gt;I used to prefer larger screens and multiple monitors. I knew about the concept of workspaces but they sounded annoying and fiddly. It took me a long time to give them a chance and it happened accidentally, as a side-effect of trying a tiling window manager.&lt;/p&gt;
&lt;p&gt;Very briefly, a tiling window manager organises your windows into tiles, meaning instead of having oddly-sized overlapping windows floating around making a confusing mess of your screen and using space inefficiently, your screen splits into fractions, each being filled by an app. If you have too many apps on the screen they can go onto other screens, or &amp;lsquo;workspaces&amp;rsquo;. Personally I tend to run most apps fullscreen most of the time, switching between them with keyboard shortcuts.&lt;/p&gt;
&lt;p&gt;I started with &lt;a href = &#34;https://i3wm.org/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;i3 Window Manager&lt;/a&gt;, which is a really nice piece of software. After a few years of using it though I realised I didn&amp;rsquo;t use most of its features, so landed on the very minimal &lt;a href = &#34;https://dwm.suckless.org/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;Suckless Tools DWM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With workspaces, each app can have its own screen and can be accessed instantly via a keyboard shortcut. With familiarity this becomes a chord in muscle memory, a hand-shape to mash at keyboard. I have habits for where I keep each app, so eg my terminal is in workspace 1 so &lt;code&gt;[Alt]+[1]&lt;/code&gt; puts me in the terminal. &lt;code&gt;[Alt]+[9]&lt;/code&gt; puts me in FireFox and so on.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve &lt;code&gt;[Alt]+[Tab]&lt;/code&gt;bed through apps in the past. Workspaces are similar except you know the keyboard chord will take you directly to the app you want, you don&amp;rsquo;t have to play &amp;lsquo;stop the magical app carousel at the right time&amp;rsquo;&amp;hellip; which may be a drawback for some I guess, as carousels can be fun.&lt;/p&gt;
&lt;p&gt;We can only focus on one thing at a time, that&amp;rsquo;s how attention works. I can see some uses for multiple screens eg if you have to monitor lots of things, like real-time charts or meters. In that case your peripheral vision is good at detecting change/motion so you can make use of all the space. But personally I&amp;rsquo;m completely over large/multiple displays. In my circumstances, they waste power, waste space, and are just not necessary.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Android Bluetooth Keyboard Mapping</title>
      <link>https://mm-dev.rocks/posts/android-bluetooth-keyboard-mapping/</link>
      <pubDate>Thu, 08 Feb 2024 12:05:30 +0000</pubDate>
      <guid>https://mm-dev.rocks/posts/android-bluetooth-keyboard-mapping/</guid>
      <description>&lt;p&gt;I use Bluetooth keyboards with Android a lot, and often want to remap keys (eg swapping &lt;code&gt;CAPS_LOCK&lt;/code&gt; to &lt;code&gt;CTRL_LEFT&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Maybe it&amp;rsquo;s a failing in my search technique or I&amp;rsquo;m doing something that nobody else does nowadays, but whenever I search for info about this I find really old articles with out-of-date info, or just links to apps. To be fair &lt;a href = &#34;https://f-droid.org/packages/io.github.sds100.keymapper/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;Key Mapper&lt;/a&gt; is good and I do use it, but sometimes I just want to be able to do it at a system level without requiring extra apps.&lt;/p&gt;
&lt;p&gt;To remap keys in Android, you need to edit a particular keylayout &lt;code&gt;*.kl&lt;/code&gt; file, specific to the device (eg Bluetooth keyboard) you want to re-map. The files have names like &lt;code&gt;Vendor_04e8.kl&lt;/code&gt;. The name is derived from various properties of the device, such as vendor ID, product ID and other things &lt;a href = &#34;https://source.android.com/docs/core/interaction/input/key-layout-files&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;as explained here in the Android docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The sticking point for me, for &lt;em&gt;ages&lt;/em&gt;, was that I had trouble finding the vendor and product codes and, consequently, the correct file to edit. In Linux I&amp;rsquo;d use &lt;code&gt;lsusb&lt;/code&gt; or &lt;code&gt;bluetoothctl&lt;/code&gt; or something like that, but on Android even in the wonderful &lt;a href = &#34;https://termux.dev/en/&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;Termux&lt;/a&gt;, these things don&amp;rsquo;t work properly for reasons I haven&amp;rsquo;t completely wrapped my head around, related to security and/or the way Android and its underlying Linux kernel inter-relate.&lt;/p&gt;
&lt;h2 id=&#34;problem-1-finding-which-keylayout-file-needs-to-be-edited&#34;&gt;Problem 1: Finding which keylayout file needs to be edited&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;In Termux or whichever shell/terminal you use, do &lt;code&gt;su&lt;/code&gt; to log in as superuser&lt;/li&gt;
&lt;li&gt;Do &lt;code&gt;dumpsys&lt;/code&gt; and wait for it to finish. It can take a while (couple of minutes on some of my devices)&amp;hellip;&lt;br&gt;
&lt;em&gt;If I don&amp;rsquo;t do this step, on some devices the next step doesn&amp;rsquo;t work&amp;hellip; I hate this kind of situation as I have no idea why this should work and it feels nonsensical, but all I know is it fixed the problem when I encountered it.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Do &lt;code&gt;dumpsys input&lt;/code&gt; to narrow the output&lt;br&gt;
&lt;em&gt;Even this input-specific command outputs quite a lot. I tend to just take my time and scroll through it, but you can make it easier by trying something like:&lt;/em&gt;&lt;br&gt;
&lt;code&gt;dumpsys input | grep -i -A12 bluetooth&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;-i&lt;/code&gt; means case-insensitive&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;-A12&lt;/code&gt; means &lt;em&gt;show the matching line plus the &lt;code&gt;12&lt;/code&gt; lines &lt;code&gt;A&lt;/code&gt;fter it&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;You may want to tweak the number but for me 12 was good&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;In the output, look for a description for your device (mine is &lt;code&gt;Device 45: Bluetooth 3.0 Keyboard&lt;/code&gt;), and soon after that you&amp;rsquo;ll find a line like &lt;code&gt;KeyLayoutFile: /system/usr/keylayout/Vendor_04e8.kl&lt;/code&gt; &amp;mdash; that&amp;rsquo;s the &lt;code&gt;*.kl&lt;/code&gt; file you need to edit to remap your keyboard&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;problem-2-editing-system-files-on-modern-android&#34;&gt;Problem 2: Editing system files on modern Android&lt;/h2&gt;
&lt;p&gt;On modern versions of Android, even if you have root, it&amp;rsquo;s not possible to directly edit files in the &lt;code&gt;/system/&lt;/code&gt; directory (it is mounted as read-only).&lt;/p&gt;
&lt;p&gt;The way around this is to &lt;a href = &#34;https://topjohnwu.github.io/Magisk/guides.html#magisk-modules&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;make a Magisk module&lt;/a&gt;. This is simpler than it might sound. The basic idea is that you create a folder in &lt;code&gt;/data/adb/modules&lt;/code&gt;, (eg in my case I use &lt;code&gt;mm&lt;/code&gt;, so &lt;code&gt;/data/adb/modules/mm/&lt;/code&gt;). Inside that folder, you can create files and folders which Magisk will overlay onto the system at boot time, eg:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/data/adb/modules/mm/system/usr
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# becomes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/system/usr
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/data/adb/modules/mm/system/usr/keylayout/Vendor_04e8.kl
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# becomes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/system/usr/keylayout/Vendor_04e8.kl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now you can edit the keylayout file to make your changes, eg to change &lt;code&gt;CAPS_LOCK&lt;/code&gt; to &lt;code&gt;CTRL_LEFT&lt;/code&gt; on my keyboard it&amp;rsquo;s:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# key 58    CAPS_LOCK&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;key &lt;span class=&#34;m&#34;&gt;58&lt;/span&gt;    CTRL_LEFT
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;other-apps-which-might-be-useful&#34;&gt;Other apps which might be useful&lt;/h2&gt;
&lt;p&gt;Depending on your Android device and software version, it can be difficult/confusing to find the right key codes. I find that one or another of the apps below usually gets me what I need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href = &#34;https://apkpure.net/hardware-keyboard-check/com.darkoak.android.hardwarekeyboardcheck&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;HardwareKeyboardCheck by Mr. Greenheart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href = &#34;https://apkpure.net/key-tester/com.flossga.android.whichbutton&#34; target = &#34;_blank&#34; rel = &#34;nofollow noopener noreferrer&#34;&gt;WhichButton aka Key Tester by FLOSS AG&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>
