<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss/styles.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>tlduck</title><description>Ben Butterworth. Building for people and planet.</description><link>https://tlduck.com/</link><item><title>Setting up Java on macOS</title><link>https://tlduck.com/blog/java-on-macOS/</link><guid isPermaLink="true">https://tlduck.com/blog/java-on-macOS/</guid><description>Set up multiple JDKs from different sources easily.</description><pubDate>Thu, 31 Dec 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;Setting up Java/ &lt;code&gt;JAVA_HOME&lt;/code&gt; happens infrequently enough that I forgot how to do it efficiently, but frequent enough that it is annoying.&lt;/p&gt;
&lt;h2&gt;Problem?&lt;/h2&gt;
&lt;p&gt;I noticed &lt;a href=&quot;https://mkyong.com/java/how-to-install-java-on-mac-osx/&quot;&gt;MKYong&apos;s method&lt;/a&gt; uses brew to install java JDK versions (recommended by OpenJDK) and then sets &lt;code&gt;JAVA_HOME&lt;/code&gt; explicitly. He doesn&apos;t use &lt;code&gt;jenv&lt;/code&gt; because of &quot;simplicity and no black box magic&quot;. MKYong modifies &lt;code&gt;JAVA_HOME&lt;/code&gt; to change the java version, and this will hide all other JAVA versions from being listed in &lt;code&gt;/usr/libexec/java_home -V&lt;/code&gt;. Also, if that path is broken, no versions show up. I suggest not modifying &lt;code&gt;JAVA_HOME&lt;/code&gt;, because in a few months when you want to switch your java version, &lt;code&gt;java_home&lt;/code&gt; will still show you which versions you have on your machine.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://github.com/jenv/jenv&quot;&gt;jenv&lt;/a&gt; does 2 things&lt;/h2&gt;
&lt;p&gt;It allows switching between Java versions and sets &lt;code&gt;JAVA_HOME&lt;/code&gt; for you. You can just read &lt;code&gt;jenv&lt;/code&gt;&apos;s Getting Started Guide now, but I&apos;ve written the rest for my convenience, since I&apos;ve set up too many machines this year (e.g. M1 Mac Mini, Intel i7 Hackintosh, MacBook Pro 16&quot; from &lt;a href=&quot;https://popsa.com/&quot;&gt;Popsa&lt;/a&gt; and my personal Intel MacBook Pro 13&quot;).&lt;/p&gt;
&lt;h2&gt;Steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Ensure Homebrew is &lt;a href=&quot;https://docs.brew.sh/Installation&quot;&gt;installed&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;code&gt;brew install --cask adoptopenjdk&lt;/code&gt;. Optional: read/ understand the &lt;a href=&quot;https://github.com/AdoptOpenJDK/homebrew-openjdk&quot;&gt;AdoptOpenJDK homebrew tap&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the tap so you can also install other versions of Java: &lt;code&gt;brew tap AdoptOpenJDK/openjdk&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the version of Java you want: &lt;code&gt;brew install --cask adoptopenjdk11&lt;/code&gt; or with any &lt;a href=&quot;https://github.com/AdoptOpenJDK/homebrew-openjdk&quot;&gt;other&lt;/a&gt; version you want.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Warning: DO NOT modify JAVA_HOME yourself, because that is what &lt;code&gt;jenv&lt;/code&gt; will do. If you&apos;re updating an older system, get rid of your old code, e.g. &lt;code&gt;export JAVA_HOME=...&lt;/code&gt; from your &lt;code&gt;.bash_profile&lt;/code&gt;, &lt;code&gt;.zprofile&lt;/code&gt;, &lt;code&gt;.zshrc&lt;/code&gt;, or wherever you might have put it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;code&gt;jenv&lt;/code&gt;: &lt;code&gt;brew install jenv&lt;/code&gt;, and follow its output: it should say copy some lines into a configuration file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;mkdir -p ~/.jenv/versions&lt;/code&gt;, to prepare the folders for jenv to work properly. This is a workaround for a &lt;a href=&quot;https://github.com/jenv/jenv/issues/167&quot;&gt;bug&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The bug: When trying to add a JAVA version to jenv using e.g. &lt;code&gt;jenv add adoptopenjdk-16.jdk/Contents/Home&lt;/code&gt;, you&apos;ll get &lt;code&gt;ln: /Users/zen/.jenv/versions/openjdk64-16.0.1: No such file or directory&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jenv add /Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home&lt;/code&gt;, remember to modify the path for your specific version. Notice the &lt;code&gt;Contents/Home&lt;/code&gt; subdirectory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add more JDKs, for example for JDK15:jenv v&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;brew install --cask adoptopenjdk15&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jenv add /Library/Java/JavaVirtualMachines/adoptopenjdk-15.jdk/Contents/Home/&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jenv versions&lt;/code&gt; to list all JDKs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jenv global jdk_name&lt;/code&gt; to set a specific JDK, which also sets the JAVA_HOME variable for you. Check it with &lt;code&gt;echo $JAVA_HOME&lt;/code&gt;. I prefer to run &lt;code&gt;jenv global 15&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;java --version&lt;/code&gt; should show the version you just set:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;openjdk 15.0.2 2021-01-19
OpenJDK Runtime Environment AdoptOpenJDK (build 15.0.2+7)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 15.0.2+7, mixed mode, sharing)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;After finding &lt;code&gt;jenv&lt;/code&gt;, I know I won&apos;t be running into setting up Java anymore. I highly recommend it.&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>How to delete many events in one go in Google Calendar</title><link>https://tlduck.com/blog/calendar-md/</link><guid isPermaLink="true">https://tlduck.com/blog/calendar-md/</guid><description>I made an Android app to bulk delete events by searching for them.</description><pubDate>Sun, 26 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;CalendarMD has one sole purpose of existence:&lt;/strong&gt; deleting events from google calendar in bulk, so I called it CalendarMD, where the MD stands for mass deleting. You search for the events in a specific calendar, and a list shows up. Scroll through the list to make sure you want to delete those events, and press delete.&lt;/p&gt;
&lt;p&gt;I made a &lt;a href=&quot;https://play.google.com/store/apps/details?id=co.fresla.bed&quot;&gt;Free Android app&lt;/a&gt; with react-native. The code is on &lt;a href=&quot;https://github.com/uxduck/calendarMD&quot;&gt;GitHub&lt;/a&gt;. Neither the UI nor the code look pretty, but it was the first app I made on React Native. Thinking back, this shouldn&apos;t have been a mobile app, and would have done better as a web app, because people are not going to install an app just to delete events from their calendar. I&apos;ve since built another app for tracking the carbon footprint of food, &lt;a href=&quot;https://play.google.com/store/apps/details?id=uk.orth.foodprint&quot;&gt;Foodprint&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=co.fresla.bed&quot;&gt;&lt;img src=&quot;./play-store.png&quot; alt=&quot;google-play&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Markdown for convenient academic writing</title><link>https://tlduck.com/blog/markdown-for-convenient-academic-writing/</link><guid isPermaLink="true">https://tlduck.com/blog/markdown-for-convenient-academic-writing/</guid><description>Read this if you think LaTex is too complicated, and you want to focus on writing content, and not practically programming the LaTex document.</description><pubDate>Thu, 28 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Read this if you think LaTex is too complicated, and you want to focus on writing content instead of formatting your the LaTex document.&lt;/p&gt;
&lt;h1&gt;Markdown &lt;code&gt;.md&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;Markdown is a standard text format that many text editors support. It&apos;s actually the format that README files use in repositories websites, like GitHub and GitLab. I use Markdown to write notes (revision notes for courses at Imperial, notes about books/ articles I read), and project work, including my Master&apos;s thesis, because it can generate LaTex documents. Below is a screenshot of what it looks like when I&apos;m writing my notes in Typora.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./osnotes.png&quot; alt=&quot;Screenshot of Typora&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When I&apos;m thinking about LaTex or Overleaf syntax, I&apos;m &lt;strong&gt;not&lt;/strong&gt; thinking about the content. In I don&apos;t want to think about back-ticks to get quotation marks correctly, copying references into a &lt;code&gt;.bib&lt;/code&gt; file, and other tedious tasks I don&apos;t want to force out from the depths of my slender Hippocampus.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Benefits&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Easy to move to a different editor&lt;/em&gt; when an editor becomes unpopular or a new editor is released with more features. Because Markdown is standard format, you can easily switch editors.
&lt;ul&gt;
&lt;li&gt;Notion and OneNote are popular, but there are issues with it. What happens when the Notion app ceases to exist, or its subscription fee is increased, or a better app gets created? You might lose all your documents, manually copy all your notes over, or be forced to pay a higher price.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Many beautiful editors available&lt;/em&gt;. I use &lt;a href=&quot;https://typora.io/&quot;&gt;Typora&lt;/a&gt; because it has the cleanest UI which lets you focus on thinking about what you&apos;re writing, but &lt;a href=&quot;https://zettlr.com/&quot;&gt;Zettlr&lt;/a&gt; is also good. Compare this to installing the huge applications like LaTex or Microsoft Word. I&apos;ve tried &lt;a href=&quot;http://overleaf.com/&quot;&gt;Overleaf&lt;/a&gt;, but this is a paid software that has reportedly had inconvenient downtime before a deadline, according to a lecturer at Imperial College.
&lt;ul&gt;
&lt;li&gt;For example, putting quotes around a word requires you ``word&quot;`. Start with a backtick and end with a double or single quote. That is extremely weird.&lt;/li&gt;
&lt;li&gt;Uploading files to Overleaf is tedious: you need to upload the file manually, and then write an LaTex figure which uses that image. It takes at least a minute, if you&apos;re good! It&apos;s not the time, its the context switching cost. With a Typora plugin it takes one drag of the mouse. Dragging an image into Typora will upload it to online storage that you configure. I have it storing images to Imgur, you can set it to store to AWS S3 and more. You&apos;ll need the UPic plugin for Typora though.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Widely used: A few blogging systems use Mardown or MDX (Markdown + JSX) for blog articles. JSX is the a markup language for React which looks similar to html. I&apos;m using MDX for this blog. If I want to publish something I was writing in Typora, I just paste it into my blog &lt;code&gt;.mdx&lt;/code&gt; file, set the publish date, and &lt;code&gt;npm run build&lt;/code&gt;. I can also add some ReactJS components too.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Drawbacks&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Syncing and collaboration: It would be great to collaborate on Markdown with others. And I still can&apos;t edit Markdown on Mobile, there doesn&apos;t seem to an application that can open sync . I might try to build a cloud service for Markdown files, with collaboration + mobile app 🙂.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;LaTex&lt;/h1&gt;
&lt;p&gt;If I&apos;m writing a real report with references, equation and Figure numbers/ captions, then I use something called Pandoc (a CLI tool &amp;amp; library) to generate the nicely formatted LaTex output without touching LaTex. The entire LaTex ecosystem is complicated. I don&apos;t want to think about formatting or even see the formatting when I&apos;m reading research papers and writing content. It breaks my train of thought. There is still a learning curve with using Pandoc. The benefit is that you can write in peace, and worry about formatting much less.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./typora.png&quot; alt=&quot;My text editor on the left, and the generated PDF on the right&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I write on the document on the left, and through a shell script, the PDF on the right is generated. In summary, Pandoc takes the markdown file, images, and reference style file (e.g. Harvard, IEEE reference style) and creates a PDF file. I don&apos;t even have the PDF open until I&apos;m proof reading and making sure the formatting looks good. When I focussing, I will also disable the sidebar. So this is what I see when I&apos;m writing this blog post right now.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./blog.png&quot; alt=&quot;My text editor on the left, and the generated PDF on the right&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Challenges I faced &amp;amp; a guide for others&lt;/h2&gt;
&lt;h3&gt;What referencing system do I use?&lt;/h3&gt;
&lt;p&gt;I use &lt;a href=&quot;https://www.zotero.org/&quot;&gt;Zotero&lt;/a&gt;, including the browser connector, which allows me to add PDFs, websites and articles to my Zotero library with one click. After using lots of open source projects in the past few years, I&apos;ve decided to use the open source instead of Mendeley, because they often work well with other open source software, for example, editors and Pandoc.&lt;/p&gt;
&lt;h3&gt;What referencing style do I use?&lt;/h3&gt;
&lt;p&gt;First I tried to find the citation style that the conference enforces. After a while of 🤔, I realised there was no standard, when I compared a few papers from the conference that I am reading papers from. I noticed &lt;code&gt;IEEE&lt;/code&gt; and &lt;code&gt;ACM&lt;/code&gt; was popular from &lt;a href=&quot;https://academia.stackexchange.com/questions/41959/is-there-a-preferred-citation-style-for-computer-science-papers&quot;&gt;StackOverflow&lt;/a&gt; and reading articles. &lt;s&gt;I went with IEEE because it puts quotation marks around the title of the paper, which I prefer.&lt;/s&gt; I now use &lt;code&gt;transactions-on-intelligent-systems-and-technology&lt;/code&gt; available on &lt;a href=&quot;https://www.zotero.org/styles?q=transactions-on-intelligent-systems-and-technology&quot;&gt;Zotero Styles&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;How do I export Markdown to LaTex, including references?&lt;/h3&gt;
&lt;p&gt;This was annoying to setup because there&apos;s a learning curve, but now it works blissfully. This guide is only for 🍏 users (Windows/ Linux users need to find an alternative to Typora). In summary, you need &lt;code&gt;Pandoc&lt;/code&gt;, Zotero, your Zotero library exported as a &lt;code&gt;.bib&lt;/code&gt; file, the &lt;a href=&quot;https://retorque.re/zotero-better-bibtex/&quot;&gt;BetterBibTex&lt;/a&gt; extension for Zotero, and style file &lt;code&gt;.csl&lt;/code&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install &lt;a href=&quot;https://www.zotero.org/&quot;&gt;Zotero&lt;/a&gt; (reference manager)&lt;/li&gt;
&lt;li&gt;Install the &lt;a href=&quot;https://chrome.google.com/webstore/detail/zotero-connector/ekhagklcjbdpajgpjgmbionohlpdbjgc&quot;&gt;Zotero connector&lt;/a&gt; extension in your browser, to add documents to your Zotero library easily, but also install Zotero things like style files&lt;/li&gt;
&lt;li&gt;Create a folder where your report will live such as images and the markdown file&lt;/li&gt;
&lt;li&gt;Export your Zotero library (remember the path/ directory to this, because you&apos;ll need it when generating the PDF with Pandoc, specifically the &lt;code&gt;--bibliography&lt;/code&gt;). (File → Export → Better BibTex) your Zotero library somewhere, into the &lt;code&gt;.bib&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;To get the citations formatted the way I wanted it, I had to install a new style I liked and that matches the conferences I am reading from to Zotero. I used &lt;a href=&quot;https://www.zotero.org/styles&quot;&gt;Zotero Style Repository&lt;/a&gt; to find the correct style. You could try searching for &lt;code&gt;IEEE&lt;/code&gt; or &lt;code&gt;ACM&lt;/code&gt;, and either install it to Zotero or download the &lt;code&gt;.csl&lt;/code&gt; file. If you installed it to Zotero, you need to export the style from Zotero into a folder somewhere, for Pandoc to use it.&lt;/li&gt;
&lt;li&gt;Install the &lt;a href=&quot;https://retorque.re/zotero-better-bibtex/&quot;&gt;BetterBibTex&lt;/a&gt; extension for Zotero&lt;/li&gt;
&lt;li&gt;Get the referencing style you want to be used on your document.&lt;/li&gt;
&lt;li&gt;Install &lt;a href=&quot;https://pandoc.org/installing.html&quot;&gt;Pandoc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Install a markdown editor, I suggest Typora&lt;/li&gt;
&lt;li&gt;Write your content!
&lt;ul&gt;
&lt;li&gt;In your markdown file, all you need to do is use the &lt;code&gt;Citation Key&lt;/code&gt;, which is provided in Zotero. Every time you want to add a reference in your document, just use &lt;code&gt;[@citationKey]&lt;/code&gt;, for example &lt;code&gt;Kolotouros et al. mentioned blah blah blah [@kolotourosConvolutionMeshRegression2019]&lt;/code&gt;. You can shorten the citation key in the Zotero settings, and I have done that now, so it looks like &lt;code&gt;[@kolotouros2019]&lt;/code&gt; instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Generate your PDF using this command. Remember to change the paths to match the files you exported/ downloaded previously.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;pandoc --filter pandoc-citeproc &quot;report.md&quot; --bibliography /Users/user/ZoteroLibrary.bib --csl /Users/user/ieee.csl -o &quot;report.pdf&quot;
open -a preview &quot;report.pdf&quot; # This will open the PDF with macOS Preview everytime you run the script
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./zotero.png&quot; alt=&quot;Screenshot of Zotero website with Citation Key highlighted in the top right&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Repeat the previous 2 steps as many times as you want. To have links in the PDF, to link from the reference in your text to the bibliography section, you should add a &lt;code&gt;.YAML&lt;/code&gt; section to the top of your markdown file. So now when you click the reference it will jump to the references section. I also added the command I used to generate the PDF so I don&apos;t have to save it somewhere else.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;link-citations: true
# More useful YAML attributes:
numbersections: true
secnumdepth: 2
number-sections: true
toc: true
lof: true
strip-comments: true
xnos-number-by-section: true
documentclass: report
---
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;How do I reference figure numbers in the text?&lt;/h3&gt;
&lt;p&gt;This is helpful if you don&apos;t want to manually update figure numbers them in caption and all the references in your text, every time a new image gets added before it. StackOverflow is your friend. &lt;a href=&quot;https://stackoverflow.com/questions/9434536/how-do-i-make-a-reference-to-a-figure-in-markdown-using-pandoc&quot;&gt;This question/ answer&lt;/a&gt; shows you. However, you&apos;ll soon find out that figures are not numbered per section, and are just ascending from 1, 2, 3 and onwards. It would be nicer to have &lt;strong&gt;Figure 1.1.3&lt;/strong&gt; instead of &lt;strong&gt;Figure 24&lt;/strong&gt;. To fix this, you can use a Pandoc extension called &lt;a href=&quot;https://github.com/tomduck/pandoc-fignos&quot;&gt;Pandoc Fignos&lt;/a&gt;, by &lt;a href=&quot;https://github.com/tomduck&quot;&gt;Thomas Duck&lt;/a&gt; on GitHub. There is a similar Pandoc extension, called &lt;a href=&quot;https://github.com/tomduck/pandoc-eqnos&quot;&gt;Pandoc Eqnos&lt;/a&gt;, by &lt;a href=&quot;https://github.com/tomduck&quot;&gt;Thomas Duck&lt;/a&gt; on GitHub.&lt;/p&gt;
&lt;h3&gt;How do I actually customise the LaTex output?&lt;/h3&gt;
&lt;p&gt;Give Pandoc a custom theme (a &lt;code&gt;.tex&lt;/code&gt; file), and it will generate the report using that theme instead of the default LaTex layout. This allows for separation of concerns (a software engineering term, aka. SoC): you can create your own custom looking LaTex files to follow a standard template someone has given you, but you don&apos;t have to even look this complex file when trying to work on the content of the report. Pandoc&apos;s documentation on theming will come in handy as you explore this topic. It can get complicated. Your LaTex file has access to all the YAML attributes at the top of the markdown file. For example, my theme uses a special front page, so I use a &lt;code&gt;title: &quot;This is the tile of my report&quot;&lt;/code&gt; in my YAML, and the theme can these use this in the LaTex file, with &lt;code&gt;$title$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For Imperial College computing, students are required to use LaTex and are recommended to use a college-provided theme for many of their projects. I have modified the theme Imperial provided us with to work with Pandoc, and it is available on &lt;a href=&quot;https://github.com/uxduck/ImperialCollegeLatexTheme&quot;&gt;GitHub&lt;/a&gt;. There is a starter report you can use, so just set up the environment following the guide above, and don&apos;t worry about LaTex or Overleaf.&lt;/p&gt;
&lt;h3&gt;Cool plugins for Typora&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;UPic&lt;/strong&gt; is a plugin that allows you to upload images to the internet purely by dragging images into the Editor. This means you don&apos;t have to worry about file directories. For example, once you have an image you like, just drag it into Typora and it will be uploaded to your online account on Imgur. UPic requires configuration to know which account to upload, so follow the documentation for this. UPic allows you to upload to Imgur, AWS S3 buckets, and more.&lt;/p&gt;
&lt;p&gt;Alfred: Alfred is a replacement for spotlight on macOS. Its a free software but the paid version has a lot more functionality. With the paid version, if I want to find a reference or a citation key in Zotero, &lt;code&gt;zot ResearchPaper&lt;/code&gt; will do that that for me.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./alfred.png&quot; alt=&quot;Alfred using the Zotero plugin&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;More documentation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pandoc.org/MANUAL.html&quot;&gt;Pandoc&apos;s&lt;/a&gt; manual is very helpful&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;fin&lt;/h2&gt;
&lt;p&gt;I hope you&apos;ve learnt something, and that your life is not consumed by LaTex, but by your research instead. I was worried about LaTex formatting a few months ago, but I&apos;m happy to say I won&apos;t be worrying ever again.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./heli.png&quot; alt=&quot;Original photo by Neil Thomas on Unsplash.&quot; /&gt;
Original photo by &lt;a href=&quot;https://unsplash.com/@neilmarkthomas?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Neil Thomas&lt;/a&gt; on Unsplash.&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Computer sciences courses I loved at Imperial College</title><link>https://tlduck.com/blog/computer-science-degree/</link><guid isPermaLink="true">https://tlduck.com/blog/computer-science-degree/</guid><description>I didn&apos;t do my undergrad in CS. I did a 1 year Master&apos;s instead</description><pubDate>Wed, 20 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;👋 Hello! This page might help you if you&apos;re deciding on doing a degree in computing, especially if you&apos;re considering Imperial College. You might be a student about to start a &lt;a href=&quot;https://www.imperial.ac.uk/study/pg/computing/computing-science/&quot;&gt;Computing Science Masters at Imperial&lt;/a&gt;, the same course I&apos;m doing right now.&lt;/p&gt;
&lt;p&gt;I&apos;m documenting what I learnt and the people that taught/ challenged me, and also reflect on my decisions so far. I&apos;ve just finished all exams for this degree, and so am concluding the &apos;taught&apos; part of the course. Onwards, to the individual project!&lt;/p&gt;
&lt;h2&gt;Courses I loved&lt;/h2&gt;
&lt;p&gt;I reserve this spot for courses with both amazing lecturers &amp;amp; content. Its no surprise all 3 lecturers below have been at Imperial for many years.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;C++ by Professor Will Knottenbelt &amp;amp; Dr Fidelis Perkonigg:&lt;/strong&gt; these lecturers did not just teach C++, but also debugging, problem solving, communication, hard work and grit! The most valuable part of the degree. Dr Perkonigg is there with you in the computer labs, and students (including me) cannot get enough of him. &lt;strong&gt;Note:&lt;/strong&gt; You get what you put in though. His guidance will be more valuable if you put more effort in programming yourself.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operating Systems by Dr Anandha Gopalan:&lt;/strong&gt; These lectures are interesting and Anandha puts them into context for beginners. Useful algorithms and concepts that show up here, repeatedly came up at developer events I attended and technical videos online: a few months ago, I would&apos;ve closed that tab. Now, I smile and think of Anandha. Anandha also edited his lecture recordings to make them more convenient to students during revision. He knows the struggle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logic by Dr Fariba Sadri:&lt;/strong&gt; This course made my brain think in weird &amp;amp; interesting ways. I intellectually enjoyed the course, and hope it will come in useful in the future. I&apos;ve gained confidence in tackling weird looking things in the future. And... truth tables ARE used in industry! However, lectures aren&apos;t recorded for Logic. I&apos;ll never know what happened in that one lecture I missed. There are weekly sessions for practicing the logic questions. If you want to do &lt;a href=&quot;https://en.wikipedia.org/wiki/Prolog&quot;&gt;Prolog&lt;/a&gt;, that can be done optionally.
&lt;img src=&quot;./swiz.png&quot; alt=&quot;A twitter post about usefulness of truth tables&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Group project supervised by Anandha Gopalan:&lt;/strong&gt; You can find more details about Foodprint &lt;a href=&quot;http://foodprint.orth.uk/&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The other courses I did&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;These were all definitely useful &amp;amp; interesting to attend the lectures for. Some are required, and some are optional.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Machine Learning&lt;/li&gt;
&lt;li&gt;Computer Vision&lt;/li&gt;
&lt;li&gt;Object oriented programming (OOP)&lt;/li&gt;
&lt;li&gt;Network &amp;amp; Web Security: I wish the tutorials weren&apos;t run in VMs.&lt;/li&gt;
&lt;li&gt;Computer Architecture&lt;/li&gt;
&lt;li&gt;Networks &amp;amp; Distributed systems (course is split in 2: Networks makes me 😻; Distributed systems part makes me 😕 because it is extremely outdated in comparison with the &lt;a href=&quot;https://www.youtube.com/channel/UC_7WrbZTCODu1o_kfUMq88g&quot;&gt;equivalent course&lt;/a&gt; at MIT).&lt;/li&gt;
&lt;li&gt;🥺 Algorithms: way too theoretical, it did not include any popular algorithms, and did barely any implementation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The other courses&lt;/h2&gt;
&lt;p&gt;There are more courses shown &lt;a href=&quot;https://www.imperial.ac.uk/computing/current-students/pg-info/mcs/&quot;&gt;here&lt;/a&gt;. For me, the available optional courses were:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Systems Verification, Logic-Based Learning, Computer Vision, Graphics, Network and Web Security, Performance Engineering, Introduction to Machine Learning, Advanced Computer Graphics, Probabilistic Inference, Computer Networks and Distributed Systems, Introduction to Symbolic Artificial Intelligence (MSc), Software Engineering, Algorithms&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Courses I wish I could do&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;There still is time. To the interwebs! I have the fundamentals now. I can learn anything with time &amp;amp; tea.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Rust&lt;/li&gt;
&lt;li&gt;Concurrency programming&lt;/li&gt;
&lt;li&gt;Distributed Algorithms&lt;/li&gt;
&lt;li&gt;Performance engineering&lt;/li&gt;
&lt;li&gt;More operating systems&lt;/li&gt;
&lt;li&gt;Compilers&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Early mistakes as a self-taught developer</title><link>https://tlduck.com/blog/early-developer-mistakes/</link><guid isPermaLink="true">https://tlduck.com/blog/early-developer-mistakes/</guid><description>I made a lot of mistakes when I first became a developer. Here&apos;s to making more of them!</description><pubDate>Sun, 19 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Read this if you want to know what mistakes I made, so you can decide for yourself if you want to make them too.&lt;/p&gt;
&lt;p&gt;Someone told me earlier today to get a good job, you can&apos;t just apply (he meant, you need to network/ have existing connections). Then I thought, he&apos;s right: I&apos;ve never gotten a job by applying, except the one at an Ice Rink. My first software job was when I was randomly called by a recruiter, and they pitched a job to me. I didn&apos;t even do Computer Science, and graduated from Engineering a few months before their call. I was spending my days, weeks and months coding, learning to become a software engineer! A few weeks later, I was a Backend Software Engineer at a really cool company. Don&apos;t worry, I &lt;em&gt;didn&apos;t&lt;/em&gt; get fired, but I made some mistakes and this article helps me reflect on what I should do now &amp;amp; beyond.&lt;/p&gt;
&lt;h3&gt;In summary&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Watching too many tutorials &amp;amp; not doing enough of my own project ideas&lt;/li&gt;
&lt;li&gt;Learning too much &amp;amp; not coding enough&lt;/li&gt;
&lt;li&gt;Irrelevant side projects and not focusing on work outside of work (maybe I should&apos;ve chosen a job that tied well with my side projects)&lt;/li&gt;
&lt;li&gt;Being distracted by everything interesting (might be a good thing?)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Watching tutorials.&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; I didn&apos;t know how important it is to just build your own project, instead of following tutorials. I confidently tell people now, &lt;strong&gt;tutorials suck my motivation&lt;/strong&gt;. That is the best way I can put it. There is no reason to learn how to implement authentication if you don&apos;t even have a project that needs it. Over time, I&apos;ve noticed other people find this too.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Never do online courses or watch tutorials &lt;strong&gt;just&lt;/strong&gt; because they sound interesting. If you hear a new technology is interesting or even programming is fun. Do it to see for yourself. Immediately go to the &apos;Getting started section&apos; of the technology or programming language. Don&apos;t watch someone else or follow along in their tutorial. Its either filled with stuff you don&apos;t know (overwhelming) or too easy (underwhelming). The more pain you&apos;re facing in your project, the more motivation you&apos;ll have to watch a few videos or read articles, and thats okay. I am problem &amp;amp; project driven. I don&apos;t know about you. PS: I still enjoy short videos like &lt;a href=&quot;https://fireship.io/&quot;&gt;Fireship&lt;/a&gt;, but I know I don&apos;t get nearly as much out of it compared to when I design and code programs myself.&lt;/p&gt;
&lt;h3&gt;Learning too much. Coding too little.&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; The codebase was overwhelming: it was a huge monolith Java application which used the publish-subscribe pattern for different components of the application to communicate. The network of these messages and communication was drawn by plugin, and it literally looked like Spaghetti 🍝. Also, there was a lot of domain specific knowledge, and I knew none of it. I also didn&apos;t know Java that well, not like I know Javascript now ❤️. Luckily for me, there was a lot of material to read: years worth of cutting edge material, in slides &amp;amp; docs. So I started there, and never really finished 😅. Thinking back, there was a lot of opportunity to just code and break more things.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My fix:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I think technical debt is an important balance in software that gets used vs one that is too slow to get developed, so I shouldn&apos;t complain at problems if I see them, but instead try to solve them. At the same time, I am relatively new, and the responsibility for keeping the developer experience good should not be on me. So in cases like this, the most aggressive thing I can do is probably speak to others to see how we can work together to improve the developer experience.&lt;/li&gt;
&lt;li&gt;I &lt;em&gt;can&apos;t&lt;/em&gt; just decide to &apos;learn faster&apos;. Learning takes time and effort, and spending time on learning takes away time from coding. Its a balance, and I probably overdid the learning part. If I&apos;m met with a situation like this, I will try to write more code, and spend less time understanding the architecture/ system when its not important for the task. I should spend more time on the individual components I need. Easier said that done though, because adding a feature in one component often requires you to understand a whole list of other components in the application. My first task was to decouple one component from everything else. Looking back, it might have been a more optimal learning curve to have given a task in one section of the code.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Irrelevant side projects?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; I love side projects because they teach you things a lot quicker than following tutorials. You can also get a feeling for the latest tech and libraries that are being written. Often though, it did not tie in with what I did in my software engineering job, and so it wasn&apos;t being augmented by my efforts outside of work. Because of this, I often thought to myself, the developer experience of so-and-so language was much better than the one we have here, and it sometimes got frustrating. Till now, Java has dissapointed me: there are way too many annotations, its slow &amp;amp; I found it generally tiring to work with. I feel like the learning curve for other languages are more interesting. However, I see Java still being used by one or two startups probably because of their employees&apos; past-experience, although I don&apos;t see it very often at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My fix:&lt;/strong&gt; In the future, I will find a role that makes me work on a technology I already know or like. When I first got that job, I didn&apos;t know many technologies, so I didn&apos;t even think about this. Focusing just on the technology the company had would have limited my understanding of computer science, especially as a &apos;self-taught/ non-computer-science graduate&apos;. Now, I&apos;ve spent some time in Python, C++ and Javascript (with React/ React Native), I&apos;ve gotten a better sense of the ecosystems. But as I learn more, I realize its less about the programming language, but more about the problem you&apos;re solving. I&apos;m still looking for companies that are solving &lt;strong&gt;interesting problems&lt;/strong&gt;, like the environmental crisis, human crises and other societal problems. However, I am careful not to work on old technologies. Personally, I more inclined to get better at making apps individually than to practice &lt;code&gt;cracking the coding interview&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Being distracted&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; So many things interest me: the CI/CD pipeline, the security vulnerabilities, and any of the applications running alongside the one I work on. I argue that spending time on these other applications give me context, and help me understand my application better.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; This &apos;problem&apos; only existed because I was relatively new: its a very interesting new world I&apos;ve entered, and its natural to want to absorb all of it. There&apos;s a benefit to understanding more things, but focus is also important. As &lt;strong&gt;&lt;em&gt;Xun Zi&lt;/em&gt;&lt;/strong&gt; said, &quot;The person attempting to travel two roads at once will get nowhere.&quot; Over time, my productivity would increase as the puzzle pieces together.&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>macOS CLIs: Building, Signing &amp; Notarizing</title><link>https://tlduck.com/blog/macos-clis/</link><guid isPermaLink="true">https://tlduck.com/blog/macos-clis/</guid><description>Building a CLI tool in Xcode, including Building, Signing and Notarizing for macOS Big Sur</description><pubDate>Sat, 27 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Once you&apos;ve built your CLI tool and you&apos;re happy with the functionality, you want to share it with others. This is called &lt;em&gt;distribution&lt;/em&gt;. You&apos;ll need the Apple Developer Program membership (&lt;code&gt;$99&lt;/code&gt;) to perform &lt;em&gt;&quot;Software distribution outside the Mac App Store&quot;&lt;/em&gt; as per &lt;a href=&quot;https://developer.apple.com/support/compare-memberships/&quot;&gt;Apple&lt;/a&gt;. This post goes over making a CLI tool in Xcode, including Building, Signing and Notarizing, on macOS Big Sur.&lt;/p&gt;
&lt;p&gt;When users download your app from outside the App Store, macOS (GateKeeper) adds an attribute to the file (the Quarantine flag). When users try to launch the downloaded application (or cli tool), GateKeeper checks that the files meet certain requirements, and either allows the user to launch the app, or restrict the launch and shows the following UI instead:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/gatekeeper.png&quot; alt=&quot;gatekeeper&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When running in the terminal, you&apos;ll get:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; ./Example\ CLI\ Tool
zsh: permission denied: ./Example CLI Tool
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;That&apos;s not a great user experience.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;code&gt;Right click &amp;gt; Open&lt;/code&gt; to bypass GateKeeper app to launch the app anyway, unfortunately we can&apos;t expect users to do this, so thats why we &lt;strong&gt;notarize&lt;/strong&gt; our app. You can check this quarantine flag in the terminal using the &lt;code&gt;xattr&lt;/code&gt; (extended attribute CLI) tool:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; xattr -p com.apple.quarantine Example\ CLI\ Tool
0081;605f4bb1;Brave;39D0D1E9-5786-4D0E-9773-9EDB45F08C69
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above output states the flag is &lt;code&gt;0081&lt;/code&gt;, the date is &lt;code&gt;605f4bb1&lt;/code&gt; which translates to &lt;code&gt;GMT: Saturday, 27 March 2021 15:13:53&lt;/code&gt; and is displayed on the UI alert, it was downloaded using the &lt;a href=&quot;https://brave.com/&quot;&gt;Brave browser&lt;/a&gt; and has a specific UUID. You can convert that Hex date into a human readable one using &lt;a href=&quot;https://www.epochconverter.com/hex&quot;&gt;EpochConverter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These requirements for a successful launch &lt;s&gt;are&lt;/s&gt; seem to be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Application installers (.&lt;code&gt;pkg&lt;/code&gt;) and their binaries (inside the .pkg) need to be &lt;strong&gt;signed&lt;/strong&gt; by the developer team to ensure they come from a specific developer team and not just created by some random person pretending to be. You sign things with certificates, not a pen.&lt;/li&gt;
&lt;li&gt;Applications files (e.g. installers, apps) need to be &lt;strong&gt;notarized by Apple&lt;/strong&gt; to ensure they are trusted. This process involves uploading it to the server through your developer account. The binaries (inside the .pkg) don&apos;t need to be.&lt;/li&gt;
&lt;li&gt;Applications need to have the &lt;strong&gt;hardened capability&lt;/strong&gt; (this is a flag set on the built executable), which limits the access of the executable, which guarantees that the application doesn&apos;t have access to certain things without permission. On Xcode, this is an opt-in feature (capability) as opposed to opt-out.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Info.plist must be present with the executable,&lt;/strong&gt; either in a parent directory or embedded in the executable. Just having notarization and signed apps will not prevent your app from being quarantined by GateKeeper. If your installer places a folder in the users machine, then you can just put the Info.plist in that folder. However, in this project, we currently just place 1 file in the user machine, in &lt;code&gt;/usr/local/bin&lt;/code&gt; so there is no place to put the Info.plist. We can embed &lt;code&gt;Info.plist&lt;/code&gt; inside the executable instead, done through Xcode project settings.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Note for future development:&lt;/strong&gt; If going to make the installer install a &lt;strong&gt;directory&lt;/strong&gt; on the users machine, you&apos;ll need to add a symbolic/ soft link in &lt;code&gt;/usr/local/bin&lt;/code&gt; pointing to executables in your directory. Otherwise the user won&apos;t be able to use it from their terminal since the executables aren&apos;t in the path.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;XCode vs. Swift Package ❌&lt;/h2&gt;
&lt;p&gt;There are 2 proper ways of creating a CLI tool for macOS. I prefer using a &lt;strong&gt;Xcode project&lt;/strong&gt; because of the extra GUI features, all those Project settings tabs: &lt;code&gt;General&lt;/code&gt;, &lt;code&gt;Signing and Capabilities&lt;/code&gt;, &lt;code&gt;Build Settings&lt;/code&gt;, &lt;code&gt;Build Phases&lt;/code&gt; and more. You get &lt;code&gt;No Editor&lt;/code&gt; in a &lt;em&gt;Swift package&lt;/em&gt; in Xcode. Some trade-offs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Xcode project&lt;/strong&gt;. Xcode can be helpful in learning and understanding the steps in the entire process, as opposed to typing everything in the terminal. Unfortunately, an Xcode project doesn&apos;t build very well with &lt;code&gt;xcodebuild clean build&lt;/code&gt;, so you&apos;ll need to use Xcode for building the application.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;code&gt;xcodebuild&lt;/code&gt; doesn&apos;t build Xcode projects very well? What&apos;s it for then??&lt;/em&gt; I don&apos;t know, I&apos;ve tried to build a few applications using &lt;code&gt;xcodebuild&lt;/code&gt; and either I don&apos;t know how to use it, or its an extremely neglected tool lacking in documentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./images/xcode_cli.png&quot; alt=&quot;xcode_cli&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Swift package:&lt;/strong&gt; If we use the swift CLI to create a swift package. We won&apos;t do this, but these are the getting started steps:&lt;code&gt;mkdir projectName&lt;/code&gt;, then &lt;code&gt;cd projectName&lt;/code&gt;, then &lt;code&gt;swift package init --type executable&lt;/code&gt;, then double click &lt;code&gt;package.swift&lt;/code&gt; (Open it with Xcode). This is nice in that the folder is more organised, e.g. it has &lt;code&gt;Sources/&lt;/code&gt; and &lt;code&gt;Tests/&lt;/code&gt;. I needed to restart Xcode because no files were showing up. This does work well with &lt;code&gt;swift build&lt;/code&gt;, so building the package is easy on the mac terminal. Unfortunately, Xcode is much less helpful.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./images/swift_cli.png&quot; alt=&quot;swift_cli&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Create Xcode project 👷‍♀️&lt;/h2&gt;
&lt;p&gt;Use Xcode &amp;gt; File &amp;gt; New &amp;gt; Project... &amp;gt; macOS &amp;gt; Command Line Tool template to create an Xcode project.&lt;/p&gt;
&lt;h2&gt;Swift Tools Support&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;What&apos;s Swift Tools Support?&lt;/em&gt; In the readme, it says &quot;Contains common infrastructural code for both &lt;a href=&quot;https://github.com/apple/swift-package-manager&quot;&gt;SwiftPM&lt;/a&gt; and &lt;a href=&quot;https://github.com/apple/swift-llbuild&quot;&gt;llbuild&lt;/a&gt;.&quot;, however I prefer Federico Zanetello&apos;s description of it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;these modules offer powerful abstractions for common [CLI related] operations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Add Swift Tools Support&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;./images/swift_package.png&quot; alt=&quot;swift_package&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In the search bar (package repository URL), add the github URL: https://github.com/apple/swift-tools-support-core&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the next window, leave them to the default &lt;code&gt;Rules&lt;/code&gt;/ settings, press Next&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pick &lt;code&gt;SwiftToolsSupport-auto&lt;/code&gt;, this is important for the binary since we want a &lt;code&gt;Library&lt;/code&gt;, not a &lt;code&gt;non Dynamic library&lt;/code&gt;. This allows the binary to be standalone, without needing extra files.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;img src=&quot;./images/repo.png&quot; alt=&quot;repo&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optional: &lt;em&gt;What&apos;s this dynamic and static linking?&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you look in &lt;code&gt;SwiftToolsSupport&lt;/code&gt;&apos;s &lt;code&gt;package.swift&lt;/code&gt;, you&apos;ll see these &lt;em&gt;package products&lt;/em&gt; described. The difference between &lt;code&gt;-auto&lt;/code&gt; and without, &lt;code&gt;SwiftToolsSupport&lt;/code&gt; has &lt;code&gt;type = .dynamic&lt;/code&gt;, which means this dependency will be dynamically linked. &lt;code&gt;-auto&lt;/code&gt; means let the swift compiler decide what to do: it conveniently chooses the static linking, we want this. Find more &lt;a href=&quot;https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html&quot;&gt;here&lt;/a&gt;. You can change this later in the project settings.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;    .library(
        name: &quot;SwiftToolsSupport&quot;,
        type: .dynamic,
        targets: [&quot;TSCBasic&quot;, &quot;TSCUtility&quot;]),
    .library(
        name: &quot;SwiftToolsSupport-auto&quot;,
      targets: [&quot;TSCBasic&quot;, &quot;TSCUtility&quot;]),
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In static linking, all code, including shared libraries are bundled in your executable, when dynamically linked, they have to be added (linked) at runtime. I&apos;ll prefer static linking because I want my compiled CLI tool to be standalone, even though it might make the binary bigger.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stack Overflow question: &lt;a href=&quot;https://stackoverflow.com/questions/1993390/static-linking-vs-dynamic-linking&quot;&gt;Static linking vs dynamic linking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And also try &lt;code&gt;man dyld&lt;/code&gt; in the terminal to get the man page for the macOS dynamic linker.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/// A library&apos;s product can either be statically or dynamically linked. It
/// is recommended to not declare the type of library explicitly to let the
/// Swift Package Manager choose between static or dynamic linking depending
/// on the consumer of the package.

taken from https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now that you&apos;ve added the repo, you can expand the dropdown menu in the file list (Project Navigator) and read the &lt;code&gt;package.swift&lt;/code&gt; file for yourself. I recommend cross referencing with &lt;code&gt;PackageDescription&lt;/code&gt; &lt;a href=&quot;https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html&quot;&gt;docs&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;⌨️🎨 Write some code 🚀🌈&lt;/h2&gt;
&lt;p&gt;Federico&apos;s post has a great section called &quot;Common Patterns&quot; &lt;a href=&quot;https://www.fivestars.blog/code/ultimate-guide-swift-executables.html&quot;&gt;here&lt;/a&gt;, including exit codes, system modules, launch arguments, iterative scripts, environment variables, pipeline messages, async calls, input parsing, and progress animations. Please have a read there, to make your CLI tool conform to expected CLI practices.&lt;/p&gt;
&lt;h2&gt;Signing and Notarization ✍️&lt;/h2&gt;
&lt;h3&gt;Set up your machine/ Xcode&lt;/h3&gt;
&lt;h4&gt;Generate Developer ID certificates&lt;/h4&gt;
&lt;p&gt;You need the &lt;strong&gt;account holder&lt;/strong&gt; of the &lt;strong&gt;Apple developer team&lt;/strong&gt; to create 2 certificates for you, &lt;code&gt;Developer ID Installer&lt;/code&gt; and &lt;code&gt;Developer ID Application&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What is a Developer ID certificate?: They are certificates for &lt;strong&gt;distribution (not developers...)&lt;/strong&gt; to identify themselves (sign their products) for delivery outside the App Store. More info &lt;a href=&quot;https://help.apple.com/developer-account/#/dev04fd06d56&quot;&gt;here&lt;/a&gt;. This is distinct to &lt;em&gt;Development Certificate&lt;/em&gt;. There are 2 types:
&lt;ul&gt;
&lt;li&gt;You need the &lt;code&gt;Developer ID Application&lt;/code&gt; certificate to sign the application/ executable&lt;/li&gt;
&lt;li&gt;You need the &lt;code&gt;Developer ID Installer&lt;/code&gt; certificate to sign the installer&lt;/li&gt;
&lt;li&gt;One Apple account (e.g. An account which costs $100/year) can create a maximum of 5 Developer ID installer certificates and 5 Developer ID application certificates. Personally, I would have named them &lt;em&gt;Distribution&lt;/em&gt; ID, not Developer ID. To summarize, all developers get a &lt;em&gt;Development Certificate&lt;/em&gt;, but there&apos;s no way all developers get a &lt;em&gt;Developer ID certificate&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Create the certificate, account holders usually already know how to do this on either &lt;a href=&quot;https://help.apple.com/xcode/mac/current/#/dev154b28f09&quot;&gt;Xcode Guide&lt;/a&gt; or &lt;a href=&quot;https://help.apple.com/developer-account/#/dev04fd06d56&quot;&gt;developer.apple.com Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Add certificate to keychain&lt;/h4&gt;
&lt;p&gt;Double-click/ open the 2 files provided by the account holder. They&apos;ve had to password protect the certificate when they generated it, so you&apos;ll need it to unlock this file. This password is not needed anymore once its in the keychain. You can delete the downloaded file they gave you too.&lt;/p&gt;
&lt;h3&gt;Configure the Xcode project for release&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Embed &lt;code&gt;Info.plist:&lt;/code&gt;&lt;/strong&gt; You need Info.plist embedded in the executable. In the target or project &lt;code&gt;Build Settings&lt;/code&gt;,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Search for &lt;code&gt;Info.plist&lt;/code&gt;, and you&apos;ll see &lt;code&gt;Create Info.plist Section in Binary&lt;/code&gt;. Set this to &lt;code&gt;Yes&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then, create an &lt;code&gt;Info.plist&lt;/code&gt; somewhere in your project (I recommend the root of the project) which has at least 3 items, &lt;code&gt;CFBundleIdentifier&lt;/code&gt;, &lt;code&gt;CFBundleName&lt;/code&gt;, and &lt;code&gt;CFBundleShortVersionString&lt;/code&gt;. I took this requirement from &lt;a href=&quot;https://eclecticlight.co/2019/06/13/building-and-delivering-command-tools-for-catalina/&quot;&gt;Howard Oakley&apos;s blog post&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&amp;gt;
&amp;lt;plist version=&quot;1.0&quot;&amp;gt;
&amp;lt;dict&amp;gt;
	&amp;lt;key&amp;gt;CFBundleIdentifier&amp;lt;/key&amp;gt;
	&amp;lt;string&amp;gt;com.example.example-cli-tool&amp;lt;/string&amp;gt;
	&amp;lt;key&amp;gt;CFBundleName&amp;lt;/key&amp;gt;
	&amp;lt;string&amp;gt;Example CLI Tool&amp;lt;/string&amp;gt;
	&amp;lt;key&amp;gt;CFBundleShortVersionString&amp;lt;/key&amp;gt;
	&amp;lt;string&amp;gt;1&amp;lt;/string&amp;gt;
&amp;lt;/dict&amp;gt;
&amp;lt;/plist&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Howard called them &lt;code&gt;Bundle Identifier&lt;/code&gt;, &lt;code&gt;Bundle Name&lt;/code&gt; and &lt;code&gt;Bundle version string (short)&lt;/code&gt;. Those are the human readable strings which Xcode shows you when it sees the &quot;raw strings&quot; in the &lt;code&gt;.plist&lt;/code&gt; file. Which is actually just an &lt;code&gt;.xml&lt;/code&gt; file. Nothing special.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;Info.plist&lt;/code&gt; File to the path, for example &lt;code&gt;$(PROJECT_DIR)/Info.plist&lt;/code&gt; if you placed this in the project root directory. Xcode will convert this into its absolute path in the UI, you don&apos;t write the absolute path yourself.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/plist.png&quot; alt=&quot;plist&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Signing configuration:&lt;/strong&gt; In &lt;code&gt;Signing and Capabilities&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Don&apos;t &apos;automatically manage signing&apos;. Uncheck this.&lt;/li&gt;
&lt;li&gt;Set the correct team (your team)&lt;/li&gt;
&lt;li&gt;Update the correct bundle identifier&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hardened Runtime:&lt;/strong&gt; In &lt;code&gt;Signing &amp;amp; Capabilities&lt;/code&gt;, Click &lt;code&gt;+ Capability&lt;/code&gt; and select &lt;strong&gt;Hardened Runtime&lt;/strong&gt;. Leave it to default settings.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Create an Archive&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Click &lt;code&gt;XCode menubar&lt;/code&gt; &amp;gt; &lt;code&gt;Product&lt;/code&gt; &amp;gt; &lt;code&gt;Archive&lt;/code&gt;. Wait for the archive to complete, and the &lt;code&gt;Organizer&lt;/code&gt; will open. You can also open it with &lt;code&gt;Xcode menubar&lt;/code&gt; &amp;gt; &lt;code&gt;Window&lt;/code&gt; &amp;gt; &lt;code&gt;Organizer&lt;/code&gt; &amp;gt; &lt;code&gt;Archives&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Distribute Content&lt;/code&gt; &amp;gt; &lt;code&gt;Built Products&lt;/code&gt; &amp;gt; Export it as a &lt;code&gt;build&lt;/code&gt; folder.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Quick check: Is your executable signed?&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Make sure executable was signed:&lt;/strong&gt; &lt;code&gt;codesign -dv --verbose=4 &quot;build/Products/usr/local/bin/Example Cli Tool&quot;&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Build package/ installer&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;pkgbuild --root build/Products \
           --identifier &quot;com.example.example-cli-tool&quot; \
           --version &quot;1.0&quot; \
           --install-location &quot;/&quot; \
           --sign &quot;Developer ID Installer: Team Name (Team ID)&quot; \
           &quot;Example CLI Tool.pkg&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;--root&lt;/code&gt; folder needs to have the files arranged in the way you want it to be installed on your users device.&lt;/li&gt;
&lt;li&gt;Notice the install location is &lt;code&gt;/&lt;/code&gt;. This is because &lt;code&gt;/usr/local/bin&lt;/code&gt; is already set as an Installation Directory in the Xcode project. If we did them in both places, you&apos;ll get &lt;code&gt;/usr/local/bin/usr/local/bin/ROOT_DIRECTORY_CONTENTS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;--sign&lt;/code&gt; value (i.e. &lt;code&gt;&quot;Developer ID Installer: Team Name (Team ID)&quot;&lt;/code&gt;) can be found in &lt;code&gt;Keychain.app&lt;/code&gt;: but look specifically for the certificate with &lt;strong&gt;Installer&lt;/strong&gt;, which you should&apos;ve created (or the account holder gave you).&lt;/li&gt;
&lt;li&gt;You can rust &lt;em&gt;pre-&lt;/em&gt; and &lt;em&gt;post-&lt;/em&gt; install scripts if you use &lt;code&gt;--scripts build/Scripts&lt;/code&gt;. Read &lt;code&gt;man pkgbuild&lt;/code&gt; for more details. This is not necessary for the most basic app, since we bundle all files needed into this one binary, and it sits inside &lt;code&gt;/usr/local/bin&lt;/code&gt;, which is already on your macOS &lt;code&gt;PATH&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alternative: Build package/ installer: use &lt;a href=&quot;http://s.sudre.free.fr/Software/Packages/about.html&quot;&gt;Packages&lt;/a&gt;&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Using the &lt;a href=&quot;http://s.sudre.free.fr/Software/Packages/about.html&quot;&gt;Packages&lt;/a&gt; app, we can configure the destination of each file in our program through a GUI. We might want to put some files in &lt;code&gt;/usr/local/bin&lt;/code&gt;, a directory in &lt;code&gt;/usr/local&lt;/code&gt;, etc. It also has a CLI, but if we can get away with not using third party app, we should.&lt;/li&gt;
&lt;li&gt;Then sign the &lt;code&gt;.pkg&lt;/code&gt; file: &lt;code&gt;productsign --sign &quot;Developer ID Installer: Team Name (Team ID)&quot; &quot;Example CLI Tool.pkg&quot; &quot;~/Desktop/Signed-Example CLI Tool.pkg&quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Quick Tests&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Make sure installer works:&lt;/strong&gt; Lets see the installer does its job, before playing around with GateKeeper and Notarization. Run the &lt;code&gt;.pkg&lt;/code&gt; file generated. You should be able to run `` from any directory. You&apos;ll need a fresh terminal (restart it).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Notarization hasn&apos;t completed:&lt;/strong&gt; I&apos;ve got three ways to check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; spctl --assess -vvv --type install &quot;Example CLI Tool.pkg&quot;
Example CLI Tool.pkg: accepted
source=Notarized Developer ID
origin=Developer ID Installer: Team Name (Team ID)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; xcrun stapler validate &quot;Example CLI Tool.pkg&quot;
Processing: Example CLI Tool.pkg
Example CLI Tool.pkg does not have a ticket stapled to it.
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;My favorite:&lt;/strong&gt; Drag the &lt;code&gt;.pkg&lt;/code&gt; file into a new browser window, and re-download it. Now try to open it, it should say: &lt;code&gt;“Example CLI Tool.pkg” can’t be opened because Apple cannot check it for malicious software.&lt;/code&gt; Even the Apple don&apos;t know about this, they suggest one of 2 ways to trigger quarantine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Email the app to yourself and use the copy that Mail downloads.&lt;/li&gt;
&lt;li&gt;Host the app on your own local or remote server and use the copy that Safari downloads.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Generate an app-specific password on Apple ID&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;To notarize the app, we could use, for example: &lt;code&gt;xcrun altool --notarize-app ... --password &quot;SENSITIVE_APPLE_ID_PASSWORD&quot; ...&lt;/code&gt;, but this reveals the password. Instead, we can add the password to the Apple keychain and refer to it as &lt;code&gt;xcrun altool --notarize-app ... --password &quot;@keychain:ITEM_NAME&quot; ...&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create an app specific password on https://appleid.apple.com/ &amp;gt; Security &amp;gt; &lt;code&gt;APP-SPECIFIC PASSWORDS&lt;/code&gt; &amp;gt; Click &lt;code&gt;Generate Password…&lt;/code&gt;. Give it a label that makes sense to you, e.g. &lt;code&gt;MacBook Pro 16&quot;&lt;/code&gt; or &lt;code&gt;Mac M1 Mini&lt;/code&gt;. This name is just for labelling it in your Apple ID account. You might want to revoke in the future when you&apos;re not using that password anymore.&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;Keychain.app&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Click File &amp;gt; New Password Item
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Keychain Item Name:&lt;/strong&gt; &lt;code&gt;Apple ID&lt;/code&gt; (or any name you prefer). You&apos;ll refer to this password item with e.g. &lt;code&gt;@keychain:Apple ID&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Account Name:&lt;/strong&gt; &lt;code&gt;$APPLE_ID_EMAIL&lt;/code&gt;, set this to your &lt;strong&gt;Apple ID email&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Password:&lt;/strong&gt; The app password generated from https://appleid.apple.com/ mentioned before.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Note: If editing an existing password item, its the &lt;code&gt;Where&lt;/code&gt; field which matters, not the &lt;code&gt;Name&lt;/code&gt; field.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Notarize package&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;To notarize the installer (&lt;code&gt;.pkg&lt;/code&gt;). &lt;a href=&quot;https://eclecticlight.co/2019/06/13/building-and-delivering-command-tools-for-catalina/&quot;&gt;A previous&lt;/a&gt; blog post mentioned that you need a &lt;strong&gt;Developer ID Distribution signature&lt;/strong&gt; but this is not necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;xcrun altool --notarize-app \
             --primary-bundle-id &quot;com.example.example-cli-tool&quot; \
             --username &quot;apple_id_email@your_domain.com&quot; \
             --password &quot;@keychain:Apple ID&quot; \
             --asc-provider &quot;APPLE_TEAM_ID&quot; \
             --file &quot;Example CLI Tool.pkg&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Where do I get &lt;code&gt;asc-provider&lt;/code&gt; value?&lt;/em&gt; It&apos;s the team ID you&apos;re in, you can find it in the developer.apple.com website, or on your certificate, or run the following command: &lt;code&gt;xcrun altool --list-providers --password &quot;@keychain:Apple ID&quot; --username &quot;apple_id_email@your_domain.com&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Check for completion: Get your request uuid from the output of the above command. You have to wait until the status message is: &lt;code&gt;Status: success&lt;/code&gt;, you get &lt;code&gt;Status message: Package Approved&lt;/code&gt;. You also get an email about the success. I am pretty sure you need to get the success response before you try doing the below stapling step.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;REQUEST_ID=SET_YOUR_REQUEST_ID_HERE
xcrun altool --notarization-info &quot;$REQUEST_ID&quot; \
             --username &quot;apple_id_email@your_domain.com&quot; \
             --password &quot;@keychain:Apple ID&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Staple the notarization ticket to the pkg file with &lt;code&gt;xcrun stapler staple &quot;Example CLI Tool.pkg&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quick tests:&lt;/strong&gt; You can &lt;strong&gt;verify&lt;/strong&gt; Notarization ticket was stapled onto your installer or executable:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;xcrun stapler validate example_cli.pkg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;or &lt;code&gt;spctl --assess -vvv --type install example_cli.pkg&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Release&lt;/h2&gt;
&lt;p&gt;You can distribute this app through Github, your personal website or other places. Users will enjoy the lack of GateKeeper UIs.&lt;/p&gt;
&lt;h2&gt;Other resources&lt;/h2&gt;
&lt;p&gt;Have a read of these resources for more context:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://eclecticlight.co/2019/06/13/building-and-delivering-command-tools-for-catalina/&quot;&gt;Building and delivering command tools for Catalina&lt;/a&gt; was useful in that it mentioned &lt;code&gt;Info.plist&lt;/code&gt; was a requirement, and demonstarted that CLI tools can be build through Xcode. However, it uses the &lt;code&gt;Packages.app&lt;/code&gt; application instead of using the command line. My post will show both ways: using Xcode with First-party CLI tools, and also briefly on &lt;code&gt;Packages.app&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I found &lt;a href=&quot;https://scriptingosx.com/2019/09/notarize-a-command-line-tool/&quot;&gt;&quot;notarize a command line tool&quot;&lt;/a&gt; very useful, but unfortunately it creates a swift package instead of an Xcode project. Therefore, a lot of other Apple and Xcode guides are not relevant. You&apos;re in the dark if things go wrong, especially since swift package manager hasn&apos;t been around as long as Xcode. This article also goes to say certain things in Howard Oakley&apos;s post are not needed, but I disagree.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Questions&lt;/h2&gt;
&lt;p&gt;Feel free to comment on this page, I&apos;ll get a notification about it and do my best to reply. I found working through notarization has been challenging and interesting, though I am not sure everyone will enjoy it.&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Using Cloudflare Products for 2 years</title><link>https://tlduck.com/blog/cloudflare/</link><guid isPermaLink="true">https://tlduck.com/blog/cloudflare/</guid><description>Cloudflare provides a generous free tier for many of their useful products, but there are some downsides even when working at small-scale.</description><pubDate>Thu, 03 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Cloudflare provides a generous free tier for many of their useful products. I list them below (and will keep them updated as I use more). Some products do have some downsides &lt;strong&gt;during development&lt;/strong&gt;, even when working at &lt;em&gt;small&lt;/em&gt;-scale. I&apos;m happy to say I overcame them, and want you to help you do the same.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Warning ⚠️:&lt;/strong&gt; This page also includes random technical details to help you solve issues. ⚠️&lt;/p&gt;
&lt;h2&gt;Cloudflare DNS&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The DNS records propagate globally within seconds, so you won&apos;t second guess your DNS configuration.&lt;/li&gt;
&lt;li&gt;The UI is much better than competing services for simple tasks like mine (e.g. AWS Route53, and other common registrars like GoDaddy, Namecheap).
&lt;ul&gt;
&lt;li&gt;Though, the Cloudflare DNS UI will probably get quite hectic if I had any more DNS records.&lt;/li&gt;
&lt;li&gt;PS: I recommend &lt;a href=&quot;https://nuttyabouthosting.co.uk&quot;&gt;nuttyabouthosting.co.uk&lt;/a&gt; if want a &lt;code&gt;.uk&lt;/code&gt; or &lt;code&gt;.co.uk&lt;/code&gt; domain), instead of GoDaddy. Great domain prices, and it just works.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Cloudflare Pages vs Netlify&lt;/h2&gt;
&lt;p&gt;Switching from Netlify to Cloudflare Pages brings 2 main benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare pages provides &lt;strong&gt;free web analytics&lt;/strong&gt;, which allows you to see where your users are coming and if they are &lt;code&gt;GoogleBot&lt;/code&gt; or not.&lt;/li&gt;
&lt;li&gt;Better &lt;strong&gt;integration&lt;/strong&gt; with other products. Its nice to reduce the number of companies I use (and will potentially pay for): I can use Cloudflare Workers, Cloudflare DNS/ Registrar, Cloudflare Workers, and I generally find more Cloudflare feature more valuable, so I&apos;ll be using more of them in the future.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Cloudflare Workers vs Firebase Cloud Functions (or AWS Lambda)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Cloudflare workers might seem like the normal serverless functions we&apos;ve heard about, like Vercel, Google/ Firebase Cloud Functions, AWS Lambda and Azure Functions are selling.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;However, Cloudflare Workers&apos; runtime is more restrictive. This might mean Cloudflare Workers are much cheaper and faster to run at scale, but I won&apos;t dig into performance today.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;They are Javascript only, to run other languages you &lt;strong&gt;need&lt;/strong&gt; to use WebAssembly. Other platforms allow you to run pretty much run anything: Google offers &lt;a href=&quot;https://cloud.google.com/run/&quot;&gt;Cloud Run&lt;/a&gt; to run serverless containers, and Lambda allows &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html&quot;&gt;custom runtimes&lt;/a&gt; to allow you to run anything you want.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cloudflare workers runs as a &lt;strong&gt;Service Worker&lt;/strong&gt; (in a V8 isolate), not as a NodeJS application or a browser application. Some libraries don&apos;t support this very well, and so this limits the use of Cloudflare Workers. For example, &lt;code&gt;XMLHttpRequest&lt;/code&gt; doesn&apos;t work. This is different to other serverless products, which run in NodeJS. The reason why Cloudflare uses Service Workers and not NodeJS is to run in V8 isolates for &quot;scale, security and speed&quot;. See Kenton Varda&apos;s &lt;a href=&quot;https://www.youtube.com/watch?v=HK04UxENH10&quot;&gt;talk&lt;/a&gt; for more.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Service Workers are specialised typed of Web Workers, specifically designed to allow intercepting network requests to act as a cache/ proxy. This allows offline use. Web Workers only have a smaller set of APIs, which won&apos;t allow you to intercept network requests, and is more designed to run Javascript off the main thread.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Whats the difference between a Service Worker and a Web Worker?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Web Worker: General work done off the main thread.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Service Worker: A special purpose runtime which is a proxy between the browser and the server. Service Workers have extra APIs for intercepting network requests. Because Cloudflare Workers intercept network requests that are incoming to a domain, they kind of fit the service worker model.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Webpack uses the same target (&lt;code&gt;webworker&lt;/code&gt;) for both of them&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;From client/ browser apps, you pass messages to it using &lt;code&gt;window.postMessage()&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Billing experience: Cloudflare Functions do not bill when the CPU is not running: i.e. It is free when you&apos;re making a network request in the cloudflare worker and waiting for the promise to return. This is different to Google Cloud/ Firebase pricing, which will definitely be more expensive:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Compute time is measured from the time your function receives a request &lt;strong&gt;to the time it completes&lt;/strong&gt;, either through you signaling completion, or through a timeout, other failure or any other termination.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The debugging experience is relatively poor: you cannot set breakpoints, run it locally, and the error messages are not that helpful. I recommend &lt;a href=&quot;https://github.com/cfworker/cfworker&quot;&gt;cfworker&lt;/a&gt; to improve the normal Cloudflare debugging experience though. My example repo which uses Ably in a Cloudflare Worker, including helpful ways to debug (e.g. &lt;code&gt;cfworker&lt;/code&gt;) exists &lt;a href=&quot;https://github.com/uxduck/ably-cloudflare-workers&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;e.g. Running &lt;code&gt;wrangler dev&lt;/code&gt; might not give you any errors. You should first try to publish the worker using &lt;code&gt;wrangler publish&lt;/code&gt;, fix the errors and confirm it works. Then &lt;code&gt;wrangler dev&lt;/code&gt; will work.&lt;/li&gt;
&lt;li&gt;Cloudflare&apos;s free TLS/SSL certs only cover the apex (orth.uk) and 1 subdomain (club2d.orth.uk). So &lt;code&gt;api.club2d.orth.uk&lt;/code&gt; doesn&apos;t get a valid SSL cert. Thanks &lt;code&gt;The Freelancer ;)&lt;/code&gt; on Discord again. So I solved this by using the default worker domain, i.e. &lt;code&gt;worker_name.your_worker_subdomain.workers.dev&lt;/code&gt;, which doesn&apos;t have this problem.&lt;/li&gt;
&lt;li&gt;Environment variables specified in &lt;code&gt;wrangler.toml&lt;/code&gt; or the cloudflare website will be accessible as global variables, not through &lt;code&gt;process.env.VAR_NAME&lt;/code&gt;, but just &lt;code&gt;VAR_NAME&lt;/code&gt;. This is confusing, because if you&apos;re using typescript, you need to ignore the warning like so:&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// @ts-ignore
console.warn(`api key is ${ABLY_PRIVATE_API_KEY}`);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;You need a DNS record pointing to the cloudflare worker route. i.e. If you want your worker to work at call.orth.uk, you need to explicitly set call.orth.uk to have a DNS record with Cloudflare &lt;em&gt;proxying&lt;/em&gt; it. Erisa on the Cloudflare Workers discord suggested setting a &lt;code&gt;AAAA&lt;/code&gt; record to &lt;code&gt;100::&lt;/code&gt;, and that worked great. The Workers dashboard does not notify you of any of this, but the documentation does.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt; To speed up development I moved my project&apos;s serverless function (I only had 1 😅) to firebase functions, mainly because of the poor development experience/ difficult debugging. I did get around to fixing my Cloudflare Worker, so there is no fundamental flaw with Cloudflare.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Cloudflare Community&lt;/h2&gt;
&lt;p&gt;Join the cloudflare discord to get for support from really helpful Cloudflare people. The community is very helpful, and they&apos;ve saved me a lot of time in debugging my Cloudflare Workers. However, with other platforms, I hardly needed to debug anything.&lt;/p&gt;
&lt;h2&gt;Question for you&lt;/h2&gt;
&lt;p&gt;Which Cloudflare products are you using or considering using? What other options are you looking at?&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Anonymous video calls in React, using Ably, Mediapipe, Three.js and Next.js</title><link>https://tlduck.com/blog/club2d/</link><guid isPermaLink="true">https://tlduck.com/blog/club2d/</guid><description>Building an anonymous video calling app without a server, using machine learning (Mediapipe), realtime pub/sub (Ably), Three.js (3D rendering) and React.js (2D/ UI rendering).</description><pubDate>Tue, 08 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You can try it at &lt;a href=&quot;https://club2d.orth.uk&quot;&gt;Club2D.orth.uk&lt;/a&gt;, but for now, you&apos;ll need to use a Desktop browser running &lt;a href=&quot;https://www.google.co.uk/chrome/&quot;&gt;Chrome&lt;/a&gt; or &lt;a href=&quot;https://www.mozilla.org/en-GB/firefox/new/&quot;&gt;Firefox&lt;/a&gt;. The repo is available &lt;a href=&quot;https://github.com/uxduck/Club&quot;&gt;here&lt;/a&gt;, which is a fork of my original repo available &lt;a href=&quot;https://github.com/ably-labs/Club&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Club2D is an anonymous video calling app inspired by the Memoji feature available on FaceTime, and allows emotions and face gestures to be shared with others without revealing someone&apos;s identity, background, disability or ethnicity. You should try building random projects which interest you because it will help you learn a lot faster than reading books or watching tutorials. Before this project, I did not know how to do 3D rendering (three.js), machine learning in the browser (MediaPipe/ TensorFlowJS), static site generation (NextJS), realtime/ event driven behaviour (Ably), nice CSS styling (TailwindCSS), and only knew a bit of ReactJS, CSS, Cloudflare and Firebase. I&apos;m quite proud of what I made, and was delighted to see many my colleagues impressed by it as well. In doing this, I managed to ask and answer questions on StackOverflow, and am also proud to see my reputation reach 6.1k.&lt;/p&gt;
&lt;h2&gt;A short demo&lt;/h2&gt;
&lt;p&gt;&amp;lt;div style={{ textAlign: &quot;center&quot; }}&amp;gt;
&amp;lt;iframe
width=&quot;900&quot;
height=&quot;506&quot;
src=&quot;https://www.youtube.com/embed/QUqt0xbigOc&quot;
title=&quot;YouTube video player&quot;
frameborder=&quot;0&quot;
allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot;
allowfullscreen&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;lt;/iframe&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Benefits over conventional video calling&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Protect identity: By using a virtual face instead of your real face, you can be anonymous in video calls&lt;/li&gt;
&lt;li&gt;Avoid bias: Your disability, ethnicity. Many conferencing apps have added background-blur.&lt;/li&gt;
&lt;li&gt;Comfort: not everyone is comfortable being in front of a camera. When in a video call, we don&apos;t know if someone has us zoomed in or is recording. It might prevent distracting other colleagues.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Technologies used&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Ably Javascript/ Typescript SDK: Sending 3D faces between users https://ably.com/&lt;/li&gt;
&lt;li&gt;MediaPipe: Getting 3D face mesh coordinates https://www.mediapipe.dev/&lt;/li&gt;
&lt;li&gt;Three.js: Rendering the 3D faces and text https://threejs.org/&lt;/li&gt;
&lt;li&gt;NextJS: Building a fast react app efficiently http://nextjs.org/&lt;/li&gt;
&lt;li&gt;React: UI Rendering and state management https://reactjs.org/&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>What ML model does Apple Vision Framework use?</title><link>https://tlduck.com/blog/apple-vision-framework/</link><guid isPermaLink="true">https://tlduck.com/blog/apple-vision-framework/</guid><description>Discovering a little more about Vision on macOS</description><pubDate>Sat, 17 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Developers can perform computer vision tasks conveniently using the &lt;a href=&quot;https://developer.apple.com/documentation/vision&quot;&gt;Vision&lt;/a&gt; framework, which is a convenient wrapper around coreML, coreML models and associated logic provided by Apple.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Optional extra reading:&lt;/strong&gt; If you haven&apos;t used coreML before, have a go at the tutorials on the &lt;a href=&quot;https://developer.apple.com/documentation/vision&quot;&gt;Apple documentation&lt;/a&gt; or build a computer vision project for yourself. &lt;a href=&quot;https://machinethink.net/blog/peek-inside-coreml/&quot;&gt;This&lt;/a&gt; is a nice deep dive into coreML, which doesn&apos;t help you use it, but allows you to compare coreML with TensorFlow and TensorFlow lite, and see how models are stored. I recommend it to people who have used coreML a few times in the past.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Discovering where the files are&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Skip to &lt;a href=&quot;#resources-directory&quot;&gt;here&lt;/a&gt; to avoid the detail about discovering where the files are.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;If you&apos;re curious about what syscalls there are on macOS:&lt;/em&gt; Run &lt;code&gt;sudo dtrace -ln &apos;syscall:::entry&apos;&lt;/code&gt; to see the system calls available on macOS, there seems to be 546.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To use &lt;code&gt;dtrace&lt;/code&gt;, we need to enable it. Restart into recovery mode.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Normal macs: Shutdown. Turn on, and hold ⌘ + R&lt;/li&gt;
&lt;li&gt;Hackintosh macs: Enter the boot picker, and select &lt;code&gt;Recovery&lt;/code&gt;, usually 2nd or 3rd in the list.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Disable system integrity protection, and optionally re-enable it without dtrace. So either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;csrutil disable&lt;/code&gt;, then &lt;code&gt;csrutil enable --without dtrace&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I ran a built-in script in one terminal window: &lt;code&gt;sudo ./usr/bin/filebyproc.d&lt;/code&gt;, and then ran my CLI tool&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In a separate terminal, run an application which uses Vision: e.g. &lt;code&gt;sudo dtrace -c image_analysis&lt;/code&gt;. A load of paths will show up: search for relevant strings: &lt;code&gt;scene&lt;/code&gt; if you&apos;re using labeling, &lt;code&gt;face&lt;/code&gt; if you&apos;re using face detection, etc. Or just search for &lt;code&gt;vision&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I found that &lt;code&gt;/System/Library/Frameworks/Vision.framework/Resources&lt;/code&gt; was being accessed&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Resources directory&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Lets &lt;code&gt;cd /System/Library/Frameworks/Vision.framework/Resources&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This directory contains &lt;a href=&quot;https://codinfox.github.io/espresso/&quot;&gt;espresso&lt;/a&gt; models, which is a &quot;A minimal high performance parallel neural network framework running on iOS&quot;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are csv, txt and plist files which describe labels, relationships/ mappings&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;berry -&amp;gt; fruit
bib -&amp;gt; clothing
bichon -&amp;gt; dog
bicycle -&amp;gt; vehicle
CVML_UNKNOWN_70 -&amp;gt; sign
billiards -&amp;gt; sport
CVML_UNKNOWN_239 -&amp;gt; games
binoculars -&amp;gt; eyeglasses
bird -&amp;gt; animal
birdhouse -&amp;gt; structure
birthday_cake -&amp;gt; holiday
camel -&amp;gt; ungulates
camera -&amp;gt; equipment
camping -&amp;gt; recreation
candle -&amp;gt; light
candlestick -&amp;gt; decoration
candy -&amp;gt; food
candy_cane -&amp;gt; holiday
candy_cane -&amp;gt; candy
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Unimportant: To optimize for specific GPUs, there are Wisdom files &lt;code&gt;Wisdom/$HARDWARE_NAME/wisdom.json&lt;/code&gt; which contain recommended parameters for specific layers in specific models.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some takeaways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I learn that Apple uses (a fork of) https://codinfox.github.io/espresso/ for their models. I guess Apple just took this CMU project internally, it hasn&apos;t been &lt;a href=&quot;https://github.com/codinfox/espresso&quot;&gt;updated&lt;/a&gt; in 5 years.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The actual model files are split into 3 files, .net, .shape and .weight files, just like any coreML model. Except in this case, its not packaged into an &lt;code&gt;.mlmodel&lt;/code&gt; file or &lt;code&gt;.mlmodelc&lt;/code&gt; directory, and its missing its .bin file (metadata).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;coremldata.bin:&lt;/strong&gt; this appears to be the model’s metadata (name of author etc) and the classification labels&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;model.espresso.net:&lt;/strong&gt; describes the structure of the model, i.e. which layers it uses and how they are connected to each other&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;model.espresso.shape:&lt;/strong&gt; the output sizes of the layers in the neural network (the same thing you saw in the output of the build step above)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;model.espresso.weights:&lt;/strong&gt; the learned parameters of the model (this is usually a big file, 96MB for Inception-v3)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;model/coremldata.bin:&lt;/strong&gt; ?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://machinethink.net/blog/peek-inside-coreml/&quot;&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;An optimisation: Also, for most files, they’re not using json, and just text files (you can append to it without parsing the entire json file, this would avoid keeping the results of the CLI in memory until the json is saved). Since a json needs to be parsed in its entirety. (and so do protobufs, which is why FlatBuffers were created for tflite).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The rest of the details are in coreML and not necessarily Vision, which is described in Matthijs Holleman&apos;s blog post about coreML &lt;a href=&quot;https://machinethink.net/blog/peek-inside-coreml/&quot;&gt;here&lt;/a&gt;. I might edit this page with more details as I get learn more about Vision.&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>FlatBuffers for TensorFlow Lite, with a bonus about quantization</title><link>https://tlduck.com/blog/flatbuffers/</link><guid isPermaLink="true">https://tlduck.com/blog/flatbuffers/</guid><description>Reading tensorflow lite models using the flatbuffer API, and understanding a little more about quantization while we&apos;re at it.</description><pubDate>Fri, 16 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;TensorFlow models are saved in the &lt;a href=&quot;https://developers.google.com/protocol-buffers/&quot;&gt;protocol buffer&lt;/a&gt; format, which is an efficient way of storing and transporting data. I first heard and used it when I worked at &lt;a href=&quot;https://popsa.com&quot;&gt;Popsa&lt;/a&gt; where we used them to speed network requests between mobile apps and the backend. I then realised TensorFlow also uses this format to store models, including the weights and metadata. Popsa had a github repo specifically used to design and agree upon the interface between the backend and the mobile applications. gRPC also uses protocol buffers as the format. &lt;a href=&quot;https://grpc.io/&quot;&gt;gRPC&lt;/a&gt; also uses protocol buffers as its format, by default. You can generate the classes to create, serialize and deserialize these objects in your preferred language with the protobuf compiler, &lt;code&gt;protoc&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of protocol buffers, TensorFlow Lite uses &lt;a href=&quot;https://google.github.io/flatbuffers/&quot;&gt;FlatBuffers&lt;/a&gt;. It has many of the benefits of protocol buffers, but without the cost of needing to deserialise the entire data file. In network requests, we don&apos;t pack that much information into a protocol buffer anyway, where a file might be 100KB. We can deserialise the entire data object very quickly. However, for ML model, we might store anywhere between 500KB to 1GB. For example, the &lt;a href=&quot;https://tfhub.dev/tensorflow/centernet/hourglass_1024x1024_kpts/1&quot;&gt;CenterNet Object and Keypoints detection model with the Hourglass backbone&lt;/a&gt; is ~695MB. It might be important to get some information out of the model without loading the entire model in memory.&lt;/p&gt;
&lt;p&gt;I wanted to read the tensorflow lite model (flatbuffers) in Python without using TensorFlow lite. &lt;a href=&quot;#note-the-easier-path&quot;&gt;There are easier ways to do this&lt;/a&gt;, but I wanted to use &lt;code&gt;flatc&lt;/code&gt;!&lt;/p&gt;
&lt;h3&gt;Getting &lt;code&gt;flatc&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;We had &lt;code&gt;protoc&lt;/code&gt; for protobufs, but I couldn&apos;t find the FlatBuffer equivalent, &lt;code&gt;flatc&lt;/code&gt; anywhere. Unfortunately, the FlatBuffers repo doesn&apos;t provide an obvious way of getting the &lt;code&gt;flatc&lt;/code&gt; binary so you can generate your class files. You can either download it from a CI job, or build it yourself.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Download it from CI:&lt;/strong&gt; Instead of building it, you can download it from most &lt;a href=&quot;https://github.com/google/flatbuffers/actions&quot;&gt;CI jobs&apos;&lt;/a&gt; &quot;Artifacts&quot; section. For example, &lt;a href=&quot;https://github.com/google/flatbuffers/actions/runs/756817539&quot;&gt;here&lt;/a&gt;, but this likely to be outdated for you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Building it yourself:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git clone git@github.com:google/flatbuffers.git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cd flatbuffers&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cmake -G &quot;Xcode&quot; -DCMAKE_BUILD_TYPE=Release&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Open the project with Xcode (&lt;code&gt;xed .&lt;/code&gt;) and build it, or just run &lt;code&gt;xcodebuild clean build&lt;/code&gt;: build all the binaries. Find the &lt;code&gt;flatc&lt;/code&gt; binary and use it, or&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optional:&lt;/strong&gt; Copy the generated binary into a directory which is already on path: &lt;code&gt;cp ./Debug/flatc /usr/local/bin&lt;/code&gt;. This means you can use &lt;code&gt;flatc&lt;/code&gt; anywhere&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Compiling TensorFlow Lite schema in Python&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Download the schema by visiting the following link, or running &lt;code&gt;wget https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/schema/schema.fbs&lt;/code&gt;. I found it by just &lt;a href=&quot;https://duckduckgo.com/?q=tensorflow+lite+model+schema&amp;amp;atb=v263-1&amp;amp;ia=web&amp;amp;iai=r1-0&amp;amp;page=1&amp;amp;sexp=%7B%22cdrexp%22%3A%22b%22%2C%22biaexp%22%3A%22b%22%2C%22msvrtexp%22%3A%22b%22%7D&quot;&gt;searching for it&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;``flatc --python schema.fbs &lt;code&gt;. Code is generated in a new directory:&lt;/code&gt;tflite/`&lt;/li&gt;
&lt;li&gt;Create a python environment and install &lt;code&gt;flatbuffers&lt;/code&gt; and &lt;code&gt;numpy&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;python3 -m venv ./venv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;. ./venv/bin/activate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pip install --upgrade flatbuffers numpy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Optional stretch goal for you: Can you modify the following script to&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Using the library&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import flatbuffers
# import numpy # Not used directly, but needed for AsNumpy function calls
from tflite.Model import Model

# Update the path to your model
model_path = &quot;./mobilenet_v2_1.0_224_quantized_1_metadata_1.tflite&quot;
with open(model_path, &quot;rb&quot;) as model_file:
  buffer = model_file.read()

# Read output tensor
model = Model.GetRootAs(buffer)

print(dir(model))

print(model.Metadata(0).Name())

output_index = model.Subgraphs(0).Outputs(0)
output_tensor = model.Subgraphs(0).Tensors(output_index)
quantization = output_tensor.Quantization()

# read its quantizationParameters
print(f&quot;Quantization: {quantization}&quot;)
q_min = quantization.MinAsNumpy()
q_max = quantization.MaxAsNumpy()
q_scale = quantization.ScaleAsNumpy()
q_zero_point = quantization.ZeroPointAsNumpy()
print(f&quot;q_min: {q_min}&quot;)
print(f&quot;q_max: {q_max}&quot;)
print(f&quot;q_scale: {q_scale}&quot;)
print(f&quot;q_zero_point: {q_zero_point}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;An &lt;em&gt;easier&lt;/em&gt; path&lt;/h2&gt;
&lt;p&gt;There is an easier way to get the quantization parameters in Python. Just load the model, and read the input or output details. If quantization parameters are set, they will be in the dictionary.&lt;/p&gt;
&lt;p&gt;So lets download a quantized model from tfhub.dev, called &lt;a href=&quot;https://tfhub.dev/tensorflow/lite-model/mobilenet_v1_0.25_128_quantized/1/metadata/1&quot;&gt;mobilenet_v1_0.25_128_quantized&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then lets prepare the python environment. TensorFlow now supports Python3.9, so this is easy.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, create a virtual environment in a directory called venv: &lt;code&gt;python3 -m venv ./venv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Load the environment: &lt;code&gt;. ./venv/bin/activate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Install tensorflow: &lt;code&gt;pip install tensorflow&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;run this script:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import tensorflow as tf

interpreter = tf.lite.Interpreter(model_path=model_path)
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;An &lt;em&gt;even&lt;/em&gt; easier path&lt;/h2&gt;
&lt;p&gt;Open it in &lt;a href=&quot;https://netron.app/&quot;&gt;Netron&lt;/a&gt;, and the quantization parameters are right there:&lt;/p&gt;
&lt;p&gt;The syntax is: &lt;code&gt;min &amp;lt;= scale * (q - zero_point) &amp;lt;= max&lt;/code&gt;. I wrote more about the syntax on &lt;a href=&quot;https://stackoverflow.com/a/67115490/7365866&quot;&gt;StackOverflow&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/netron.png&quot; alt=&quot;Screenshot of netron app, which the quantization field on the right side bar&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At least now, you know where they sit in the TensorFlow model file, which is actually flatbuffer file with a &lt;code&gt;.tflite&lt;/code&gt; extension.&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>How to debug Android apps (including Flutter) without launching from the debugger?</title><link>https://tlduck.com/blog/debug-android-and-flutter-from-launch/</link><guid isPermaLink="true">https://tlduck.com/blog/debug-android-and-flutter-from-launch/</guid><description>How does Flutter manage to run on iOS and Android?</description><pubDate>Sun, 12 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;You may want to observe/ debug behaviour related to your app&apos;s Activity, Broadcast Receiver or Service being launched by an Intent, without any side effects of the application already running. For example, I was testing push notifications, and wanted to debug the case where &lt;strong&gt;no other application components was running (Activity, Service)&lt;/strong&gt;, and only the Broadcast Receiver declared in my &lt;code&gt;AndroidManifest.xml&lt;/code&gt; was launched in response to the push notification message. This is only possible if the application hasn&apos;t been launched yet by Android Studio.&lt;/p&gt;
&lt;p&gt;The trick is prevent your app from launching until the debugger is attached, and then to attach the debugger. This means you won&apos;t miss anything between the app launching and you attaching the debugger.&lt;/p&gt;
&lt;h2&gt;Steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Ensure your app is installed&lt;/li&gt;
&lt;li&gt;Enable and configure &lt;strong&gt;Developer options&lt;/strong&gt;. In &lt;strong&gt;Developer options&lt;/strong&gt;,
&lt;ul&gt;
&lt;li&gt;Enable &lt;strong&gt;USB debugging&lt;/strong&gt;. This allows you t debug your apps, and to use adb to copy files, check processes running on your device, etc.&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;Select debug app&lt;/strong&gt;, and select your application.&lt;/li&gt;
&lt;li&gt;Then enable &lt;strong&gt;Wait for debugger&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;You screen should look something like:&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;select_debug_app.png&quot; alt=&quot;select_debug_app&quot; /&gt;&lt;/li&gt;
&lt;li&gt;Optional: enable &lt;strong&gt;Stay Awake&lt;/strong&gt;. This is nice to avoid your Android from locking while you are trying to run and test apps on your device. This could get annoying though, for example when you say &quot;Ok Google&quot;, the screen will turn off but won&apos;t turn off, so you should remember to turn this off at the end of the day.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Trigger your app to launch&lt;/h2&gt;
&lt;p&gt;If you are trying to test push notifications, send a push notification to your device. For example, I have an iOS application which can send a push notification to other devices. Your app should not actually launch, but it would be &lt;em&gt;waiting for the debugger&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;waiting_for_debugger.png&quot; alt=&quot;waiting_for_debugger&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Flutter only&lt;/h2&gt;
&lt;p&gt;If you are working on Flutter, you should have 2 Android Studio windows open:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One window for the project (root project directory, &lt;code&gt;project_dir&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;One window for the android project (&lt;code&gt;project_dir/android&lt;/code&gt;, or for flutter package plugins, &lt;code&gt;project_dir/example/android&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point, you should press &lt;strong&gt;Flutter attach&lt;/strong&gt; in your &lt;code&gt;project_dir&lt;/code&gt; Android Studio window first. Nothing will happen until after you attach your Android app debugger, below. You should currently only see, for example &lt;em&gt;&quot;Waiting for a connection from Flutter on SM G991B...&quot;&lt;/em&gt;. &lt;strong&gt;SM G991B&lt;/strong&gt; is my physical device, and Samsung S21.&lt;/p&gt;
&lt;h2&gt;Attach your debugger&lt;/h2&gt;
&lt;p&gt;Click the &lt;strong&gt;Attach debugger to Android Process&lt;/strong&gt;, and in the window, select your application by its application ID. Your application should launch, and your debugger breakpoints will work. If running Flutter, your Flutter debugger in Android Studio will also&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;choose_process.png&quot; alt=&quot;choose_process&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Tips/ warnings&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;🐛 If &lt;strong&gt;Wait for debugger&lt;/strong&gt; is enabled, sometimes you need to detach or close Android Studio&apos;s debugger and re-attach it if you want to handle a subsequent application launch successfully. Otherwise, the app would never launch.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Other nice tools&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Genymobile/scrcpy&quot;&gt;scrcpy&lt;/a&gt;: A great tool to show your Android screen on your computer.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/JakeWharton/pidcat&quot;&gt;pidcat&lt;/a&gt;: A logcat which is easier to read and use, maybe.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>A case against Symlinks in Xcode</title><link>https://tlduck.com/blog/symlinks-in-xcode/</link><guid isPermaLink="true">https://tlduck.com/blog/symlinks-in-xcode/</guid><description>A list of challenges you face when using Symlinks in Xcode</description><pubDate>Mon, 21 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve seen a few &lt;em&gt;complaints&lt;/em&gt; 😈 related to symlinks in Xcode dotted around the internet, but nothing put things into perspective as much as me trying to use symlinks in Xcode, myself.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A small price to pay to avoid having to mess with symlinks which cause their own issues in Xcode _ - &lt;a href=&quot;https://joesusnick.medium.com/swift-package-manager-with-a-mixed-swift-and-objective-c-project-part-2-2-e71dad234e6&quot;&gt;&lt;strong&gt;Joe Susnick&lt;/strong&gt;&lt;/a&gt;_&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;The package no longer appears as a package within Xcode (icon is like a terminal-stamped file), and it&apos;s not built anymore. - &lt;em&gt;&lt;a href=&quot;https://forums.swift.org/t/symlink-to-spm-package-no-longer-builds-in-xcode-12-5-was-ok-on-12-4-earlier/47939&quot;&gt;&lt;strong&gt;Raphael Sebbe&lt;/strong&gt;&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;I wondered what Joe meant 🤔...&lt;/h2&gt;
&lt;p&gt;this might be why:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You cannot save the file in Xcode when you opened the file via its symlink. Developers might open the file in Xcode by clicking the symlink, since it appears to be a normal file. It&apos;s possible to open the file, edit it, but not to &lt;em&gt;save&lt;/em&gt; it, you&apos;ll get the popup shown below. Once this happens, you also cannot edit the original file (via its original hardlink), because Xcode is storing the modified version of the symlink file. To resolve this, you need to force quit Xcode. It won&apos;t close normal (pressing the red/ close menubar item won&apos;t work).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/24711048/122800047-624e9800-d2ba-11eb-96dd-84c94e75c826.png&quot; alt=&quot;CleanShot 2021-06-21 at 17 59 08&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Annoying popups&lt;/strong&gt;: Upon every rebuild after trying to save the file, you&apos;ll get this popups twice, one after the other.&lt;/li&gt;
&lt;li&gt;**Xcode incremental builds fail: **Xcode doesn&apos;t know if the file (the one that the symlink points to) is &lt;strong&gt;changed&lt;/strong&gt;. So the build won&apos;t work properly, because it won&apos;t recompile that file. It fools Xcode build system. Even after cleaning the project, I can&apos;t get it to recompile (even after restarting Xcode). The solution is to delete the symlink from not just the Xcode project, but the actual symlink, and re-create it. So when a user modified the header (even via the original file), the target which relies on the symlink version of the file will not notice, and so this file won&apos;t get recompiled. You have to clean the project/ build files &lt;code&gt;Product&lt;/code&gt; &amp;gt; &lt;code&gt;Clean&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mental overhead:&lt;/strong&gt; An Xcode project already has its own meaning of files: instead of directly using the directory structure, it has references, so you can put your files in any folder structure in Xcode. It&apos;s only visible inside Xcode. Putting another layer of indirection via symlinks adds another layer of indirection that developers have to think about. Keep your project structure simple.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; Xcode gets &lt;strong&gt;very&lt;/strong&gt; confused by symlinks, and if you&apos;re not convinced, I suggest you play around with symlinks in Xcode. For those who understand how to use Xcode with symlinks, its a pain to clean and rebuild (losing incremental builds) and a small bit of mental overhead to ensure you check you&apos;re editing a hardlink. For someone joining your project, its a booby trap. May Tim Cook help you 😅.&lt;/p&gt;
&lt;h2&gt;Why I was considering Symlinks&lt;/h2&gt;
&lt;p&gt;I wanted to share code/ files between modules (targets) in a Swift Package, a question I posted on the &lt;a href=&quot;https://forums.swift.org/t/swift-packages-sharing-code-between-libraries-while-exposing-some-apis-from-the-shared-code/49605/2&quot;&gt;Swift forums&lt;/a&gt;, and Swift doesn&apos;t compile if it detects 2 files being shared across 2 targets. Fortunately:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I think you can trick SwiftPM using symlinks. - &lt;a href=&quot;https://github.com/SDGGiesbrecht&quot;&gt;&lt;strong&gt;Jeremy David Giesbrechts&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Though I was able to use symlinks in Xcode, Xcode did not treat me very well, with the problems I listed in this page. The other alternative is to save the shared file in module, and share that module between the modules that want to use it.&lt;/p&gt;
&lt;h2&gt;Another reason you might use Symlinks&lt;/h2&gt;
&lt;p&gt;You want to move your header files into the &lt;code&gt;include/&lt;/code&gt; in preparation for Swift Package Manager support for your Objective-C library. I recommend moving the files into the &lt;code&gt;include/&lt;/code&gt; directory, instead of creating symlinks, to avoid the issues listed above. If you&apos;re looking for a great Swift Package to model your project structure on, look at &lt;a href=&quot;https://github.com/SDGGiesbrecht/SDGCornerstone&quot;&gt;SGDCornerstone&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Why you shouldn&apos;t use hardlinks&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;hard links cannot be represented in git - &lt;a href=&quot;https://stackoverflow.com/questions/3729278/git-and-hard-links&quot;&gt;&lt;strong&gt;Stack Overflow&apos;s Jakub Narębski&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That should be enough. 😅&lt;/p&gt;
&lt;p&gt;PS: I used Xcode 13 beta.&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>The issue with standardisation</title><link>https://tlduck.com/blog/standardisation-sucks/</link><guid isPermaLink="true">https://tlduck.com/blog/standardisation-sucks/</guid><description>Bad standards are easy to add, but expensive to maintain. Good standards are hard to add, but saves money.</description><pubDate>Wed, 15 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I work in a team that manages far more software libraries than there are people. I wanted to raise my concern about &lt;strong&gt;standardisation&lt;/strong&gt; across projects/ libraries (e.g. readmes, tools, github workflow file naming conventions). For example, synchronizing all the projects to have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the same README structure/ template.&lt;/li&gt;
&lt;li&gt;using &lt;a href=&quot;https://asdf-vm.com/&quot;&gt;ASDF&lt;/a&gt;, a new runtime manager tool for as many libraries as possible, instead of using &lt;a href=&quot;https://github.com/rbenv/rbenv&quot;&gt;rbenv&lt;/a&gt;, &lt;a href=&quot;https://github.com/nvm-sh/nvm&quot;&gt;nvm&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Calling any CI workflow &lt;code&gt;check.yaml&lt;/code&gt; if they are related to tests, static analysis, unit tests or integrations tests.&lt;/li&gt;
&lt;li&gt;Sharing the same features between all libraries.&lt;/li&gt;
&lt;li&gt;Sharing the same API/ interface between all libraries.&lt;/li&gt;
&lt;li&gt;Sharing an &lt;strong&gt;implementation guide/ document&lt;/strong&gt;, where all developers are required to implement features in the exact same way, including testing their applications in the same way.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Benefits/ costs&lt;/h2&gt;
&lt;p&gt;What are the benefits (and costs) of doing these things? An engineer did create these, so there&apos;s is actually a benefit to considering them. Generally, it avoids &quot;duplicate&quot; thinking (dummy-proofing).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The README will be more familiar to people (developers, customers) as they move between projects.
&lt;ul&gt;
&lt;li&gt;Cost: No README is perfect, and structure is part of that. Developers should feel free to structure the README. Additionally, if a documentation site is created for one library in particular, we might nuke the README completely. A developer will second guess adding a section that they believe is important. If they want to improve the structure by removing a useless section or make structural changes to the README, they might choose to seek permission from the standard setter, or I predict, make no improvements to the docs (accept the fate, and give up).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;asdf&lt;/code&gt;: Developers can easily drop in and out of SDKs (mercenary style) and run the runtimes.
&lt;ul&gt;
&lt;li&gt;Cost: Of course, using an uncommon lowest common denominator tool might bring use outdated runtimes/ have bugs/ worse developer experience. Node version manager, and &lt;code&gt;Ruby&lt;/code&gt; are tools designed specifically for that runtime, are already established.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Calling any CI workflow file: &lt;code&gt;check.yaml&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;Cost: Each workflow should have a meaningful name. If there are 2 workflows, one for unit tests, and the other for integration tests, it would ideally be clear from the filename itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It can be detrimental and restrict the improvement/ growth than each repo/ project can make, compared to if repos didn’t have this standardisation cost. Developers have to “argue” their case for breaking the standard, when the standard was never justified very well or discussed. IMHO there are much more important things to think about.&lt;/p&gt;
&lt;p&gt;Once the decision is made to create a standard, there might still be a cost to design a standard that is suitable. For example, if creating a consistent README structure, work has to be done to create a suitable README that fits all libraries. Problem alert?: This structure might be the lowest common denominator or contain unnecessary sections.&lt;/p&gt;
&lt;p&gt;When a standard is put in place, you&apos;ll find lots of things that &lt;em&gt;don&apos;t&lt;/em&gt; conform to the standard. I&apos;ve noticed my team lead who has many years of development experience working on this type of non-technical work. Apart from this synchronising cost between libraries when the standard is first enacted/ discovered, I think there is a hidden, but much greater cost of maintaining this synchronicity. Developers are restricted in choosing their tools or an approach in their project, which &lt;a href=&quot;https://www.gsb.stanford.edu/insights/netflix-founder-reed-hastings-make-few-decisions-possible&quot;&gt;makes the process dummy-proof&lt;/a&gt;. Reed Hasting&apos;s (Netflix founder) says &quot;if you dummy-proof the process, you only get dummies to work there&quot;. Importantly, when a new tool or technology comes out, we’d be stuck with a standard, and the onus is on the developer to argue the case. A huge amount of work would be on us to decide to upgrade the standard, and make a new standards leap (upgrade to the new tool). This is unnecessary friction, not all developers voice their concerns like I do, and defaults are powerful.&lt;/p&gt;
&lt;p&gt;I believe avoiding this type of cost is actually in some ways a Netflix principle. Read no rules rules, by Reed Hastings :wink:&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;So I say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Bad standards are easy to add, but expensive to maintain. Good standards are hard to add, but saves money.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is part of my Engineering principle to just &lt;strong&gt;keep things simple&lt;/strong&gt;. Synchronisation between projects is not simple.&lt;/p&gt;
&lt;p&gt;For every instance of standardisation, there should be a clear argument for (benefits vs. costs) and a discussion about it, before it gets made a standard. And along this standard document, a justification should live, where future developers can question and remove the justifications if they are no longer necessary. Make it harder for the dummy-proof process designers to create their processes, so developers can keep our autonomy and productivity.&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>What is the difference between a clang (C++, Objective-C, C) module and a Swift module?</title><link>https://tlduck.com/blog/clang-module-vs-swift-module/</link><guid isPermaLink="true">https://tlduck.com/blog/clang-module-vs-swift-module/</guid><pubDate>Tue, 21 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;They are different.&lt;/em&gt; At the end of the build process though, they both need to be linked to your application/ library&apos;s other &lt;code&gt;.o&lt;/code&gt; and &lt;code&gt;.dylib&lt;/code&gt; files for it to run.&lt;/p&gt;
&lt;h3&gt;Swift modules&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/apple/swift/blob/main/docs/Serialization.md&quot;&gt;From Swift Serialization.md docs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The fundamental unit of distribution for Swift code is a module. A module contains declarations as an interface for clients to write code against.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html&quot;&gt;Swift acccess control docs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A module is a single unit of code distribution: a framework or application that’s built and shipped as a single unit and that can be imported by another module with Swift’s import keyword.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configured by &lt;code&gt;.target()&lt;/code&gt;&apos;s in &lt;code&gt;Package.swift&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cannot have submodules, so users cannot &lt;code&gt;import Module.Submodule&lt;/code&gt; in Swift. Users can still import specific entities, &lt;code&gt;import struct PackageModel.Manifest&lt;/code&gt;, but this is a lot more verbose than importing submodules.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Its interface exists as a &lt;code&gt;.swiftmodule&lt;/code&gt;. &lt;a href=&quot;https://forums.swift.org/t/whats-in-the-file-of-swiftmodule-how-to-open-it/1032&quot;&gt;What is a .&lt;code&gt;swiftmodule&lt;/code&gt;?&lt;/a&gt;. The &lt;a href=&quot;https://github.com/apple/swift/blob/main/docs/Serialization.md&quot;&gt;documentation&lt;/a&gt; says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Conceptually, the file containing the interface for a module serves much the same purpose as the collection of C header files for a particular library.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;blockquote&gt;
&lt;p&gt;The compiler produces this &lt;code&gt;.swiftmodule&lt;/code&gt; file a lot, like a generated objective-C header, but instead of text, its a binary repesentation. It includes the bodies of inlinable functions, much like static inline functions in objective-C or header implementations in C++. However, Swift modules does include the names and types of private declarations. This allows you to refer to them in the debugger, but it does mean you shouldn&apos;t name a private variable after your deepest darkest secret. &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/415/&quot;&gt;from WWDC 2018: Behind the Scenes of the Xcode Build Process&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;So private declarations are exposed in your &lt;code&gt;.swiftmodule&lt;/code&gt; (Swift module interface).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When importing pure Objective-C frameworks into Swift, the Swift compiler uses its built-in clang compiler to import an Objective-C header.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The importer finds declarations in the headers exposed in Clangs &lt;code&gt;.modulemap&lt;/code&gt; for that framework. (again, from WWDC2018)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When importing Objective-C + Swift frameworks into Swift, the Swift compiler uses the Umbrella header.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Clang modules&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Configured by &lt;code&gt;YourModuleName.modulemap&lt;/code&gt; file (previously &lt;code&gt;module.map&lt;/code&gt;, but this is deprecated), formatted like &lt;a href=&quot;https://clang.llvm.org/docs/Modules.html#module-map-language&quot;&gt;this&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Can have submodules, e.g. &lt;code&gt;std&lt;/code&gt; module has &lt;code&gt;std.io&lt;/code&gt; and &lt;code&gt;std.complex&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A clang module exposes header files specified in the module map. Private details (in &lt;code&gt;.m&lt;/code&gt;) are not exposed at all.&lt;/li&gt;
&lt;li&gt;Is an improvement of the original &lt;code&gt;#include&lt;/code&gt; or &lt;code&gt;#import&lt;/code&gt; style imports to improve the build process (This is a big topic, read the &lt;a href=&quot;https://clang.llvm.org/docs/Modules.html#introduction&quot;&gt;Clang module docs&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;I originally posted this question and answer on Stack Overflow &lt;a href=&quot;https://stackoverflow.com/questions/68055447/what-is-the-difference-between-a-clang-objective-c-module-and-a-swift-module&quot;&gt;here&lt;/a&gt;. I had to do some digging and reading for this one.&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Debugging Push Notifications on iOS</title><link>https://tlduck.com/blog/ios-push-notifications-debugging/</link><guid isPermaLink="true">https://tlduck.com/blog/ios-push-notifications-debugging/</guid><description>A lot can go wrong, this post should give you the tools to find out what is going wrong, so you can ask the right questions and fix it.</description><pubDate>Wed, 22 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The real value/uniqueness on this page is showing how to use &lt;a href=&quot;#reading-device-logs&quot;&gt;Console.app for push notifications&lt;/a&gt;. I also include some more basic guidance to help you avoid some common problems.&lt;/p&gt;
&lt;h2&gt;Use the debugger&lt;/h2&gt;
&lt;p&gt;Launch the application from Xcode (run, but ensure your scheme uses the &lt;strong&gt;Debug&lt;/strong&gt; build configuration, not release or profile) or AppCode (debug mode). Place breakpoints in methods that should be called to confirm that your methods are (not) being called. Some useful methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alert notification:
&lt;ul&gt;
&lt;li&gt;Note: If you send a message without &lt;code&gt;data&lt;/code&gt;, you won&apos;t be able to handle the notification in &lt;code&gt;didReceiveRemoteNotification&lt;/code&gt; below. Instead, you can handle notification taps by the user, and decide to even show it if the app is in the foreground.&lt;/li&gt;
&lt;li&gt;Notification tap: &lt;a href=&quot;https://developer.apple.com/documentation/usernotificationsui/unnotificationcontentextension/1845197-didreceivenotificationresponse&quot;&gt;&lt;code&gt;didReceiveNotificationResponse:completionHandler:&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Foreground alert notifications: &lt;a href=&quot;https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649518-usernotificationcenter&quot;&gt;&lt;code&gt;userNotificationCenter(_:willPresent:withCompletionHandler:)&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Background notifications: &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623013-application&quot;&gt;&lt;code&gt;application(_:didReceiveRemoteNotification:fetchCompletionHandler:)&lt;/code&gt;&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Don&apos;t use &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623117-application&quot;&gt;&lt;code&gt;application(_:didReceiveRemoteNotification:)&lt;/code&gt;&lt;/a&gt;, the deprecated version.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Request authorization from the user&lt;/h2&gt;
&lt;p&gt;If showing an alert notification to the user, be sure to &lt;a href=&quot;https://developer.apple.com/documentation/usernotifications/unusernotificationcenter/1649527-requestauthorization&quot;&gt;&lt;code&gt;requestAuthorization&lt;/code&gt;&lt;/a&gt; to show notifications from the user. This is &lt;strong&gt;not&lt;/strong&gt; required if you just want background messages, handling messages in &lt;a href=&quot;https://developer.apple.com/documentation/usernotificationsui/unnotificationcontentextension/1845197-didreceivenotificationresponse&quot;&gt;&lt;code&gt;didReceive(_:completionHandler:)&lt;/code&gt;&lt;/a&gt;. It&apos;s really easy to forget this.&lt;/p&gt;
&lt;h2&gt;Try sending it directly to APNs&lt;/h2&gt;
&lt;p&gt;You should do this when first debugging. This allows you to test push notifications without having to structure a message correctly, since this file does it for you. This also reduces the complexity/ layers in the problem when first debugging, since 3rd party services (&lt;a href=&quot;https://ably.com/documentation/general/push/publish#channel-broadcast&quot;&gt;Ably&apos;s Channels&lt;/a&gt; or &lt;a href=&quot;https://firebase.google.com/docs/cloud-messaging/&quot;&gt;Firebase Cloud Messaging&lt;/a&gt;). If the device is registered correctly, it should receive this message. Make sure to update the &lt;code&gt;TEAMID&lt;/code&gt;, &lt;code&gt;KEYID&lt;/code&gt;, &lt;code&gt;SECRET&lt;/code&gt;, &lt;code&gt;BUNDLEID&lt;/code&gt; and &lt;code&gt;DEVICETOKEN&lt;/code&gt;. My colleague shared this script with me:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Usage:&lt;/strong&gt; Get &lt;code&gt;direct_apns.sh&lt;/code&gt; from this &lt;a href=&quot;https://gist.github.com/uxduck/14e4ab65d4e85faa9b6214e0f8f049af&quot;&gt;GitHub Gist&lt;/a&gt;. Run &lt;code&gt;chmod +x direct_apns.sh&lt;/code&gt; and execute it: &lt;code&gt;./direct_apns.sh&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the script failed with an error, check the error which is returned by APNs.&lt;/li&gt;
&lt;li&gt;If the script was successful but it doesn&apos;t appear to arrive on the device, time to read the logs. (See below)&lt;/li&gt;
&lt;li&gt;If this arrives on the device, but your own messages don&apos;t, this means you are not structuring your message correctly. For example, I&apos;ve seen users who &lt;a href=&quot;https://stackoverflow.com/a/69057098/7365866&quot;&gt;set &lt;code&gt;apns-headers&lt;/code&gt; in Firebase at the wrong level of the JSON&lt;/a&gt; being sent to Firebase.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Reading device logs&lt;/h2&gt;
&lt;p&gt;Open &lt;strong&gt;Console.app&lt;/strong&gt; (this is different to Terminal.app or iTerm2.app) installed on your mac. To confirm your application received the push message/ check for errors related to push notifications:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;console-1.png&quot; alt=&quot;Console.app showing a successful push notification&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Be sure to start logging for the correct dvice. Then find relevant logs by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;search for the following log messages:
&lt;ul&gt;
&lt;li&gt;Both failures and success: &lt;code&gt;com.apple.pushLaunch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Failures only: &lt;code&gt;CANCELED: com.apple.pushLaunch&lt;/code&gt;. For example, this may show the log line: &lt;code&gt;CANCELED: com.apple.pushLaunch.com.example.app:DBA43D at priority 10&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Success only: &lt;code&gt;COMPLETED com.apple.pushLaunch&lt;/code&gt;. For example, this may show the log line &lt;code&gt;COMPLETED com.apple.pushLaunch.package_name:XXXXXX at priority 5&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;filter for &lt;code&gt;dasd&lt;/code&gt; process either by right clicking a log line with &lt;code&gt;dasd&lt;/code&gt; and click &lt;code&gt;Show Process &apos;dasd&apos;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If you are sending a background notification, it may be throttled by iOS. In this case, an error will be shown in Console.app, but will eventually arrive to your application, where &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623013-application&quot;&gt;&lt;code&gt;didReceiveRemoteNotification&lt;/code&gt;&lt;/a&gt; delegate method will be called, often within a few minutes. If you look in the Console.app logs, you may find sending the exact same message gives different outcomes:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ThunderingHerdPolicy&lt;/code&gt; error (&lt;a href=&quot;https://stackoverflow.com/questions/68983220/ios-push-notifications-what-is-thunderingherdpolicy&quot;&gt;What really is the &lt;code&gt;ThunderingHerdPolicy&lt;/code&gt;?&lt;/a&gt;):&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;{name: ThunderingHerdPolicy, policyWeight: 1.000, response: {Decision: Must Not Proceed, Score: 0.00, Rationale: [{deviceInUse == 1 AND timeSinceThunderingHerdTriggerEvent &amp;lt; 900}]}}
  ], FinalDecision: Must Not Proceed}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cameraIsActive&lt;/code&gt; error:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;com.apple.pushLaunch.io.ably.flutter.plugin-example:4935F4:[
{name: MemoryPressurePolicy, policyWeight: 5.000, response: {Decision: Must Not Proceed, Score: 0.00, Rationale: [{cameraIsActive == 1}]}}
], FinalDecision: Must Not Proceed}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;A successful delivery:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;{name: ApplicationPolicy, policyWeight: 50.000, response: {Decision: Absolutely Must Proceed, Score: 1.00, Rationale: [{[appIsForeground]: Required:1.00, Observed:1.00},]}}
  ], FinalDecision: Absolutely Must Proceed}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Another successful delivery:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;com.apple.pushLaunch.io.ably.flutter.plugin-example:5E1C66:[
{name: DeviceActivityPolicy, policyWeight: 5.000, response: {Decision: Can Proceed, Score: 0.25}}
] sumScores:93.270000, denominator:97.020000, FinalDecision: Can Proceed FinalScore: 0.961348}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I initially wrote this to add Push Notifications support to a Flutter library. There might be more useful content &lt;a href=&quot;https://github.com/ably/ably-flutter/blob/main/PushNotifications.md&quot;&gt;there&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;A note about environments (APNs sandbox vs production)&lt;/h2&gt;
&lt;p&gt;There are 2 environments, sandbox and production.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sandbox:&lt;/strong&gt; When running your application via Xcode or your machine (Android Studio, command line), your application runs in either debug, profile or release mode. In all cases, your application will use the sandbox/development APNs environment. Also, If distributing through Development, the sandbox / development APNs environment is used.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Production:&lt;/strong&gt; When distributing your app in the App Store, Ad Hoc or through App Store Connect, it will always use the production environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;More problems?&lt;/h2&gt;
&lt;p&gt;Comment below with your issues and I&apos;ll try to help. APNs can be quite a frustrating experience, and I understand it a bit deeply now, and I&apos;m happy to help. Or ask a Stack Overflow question and link it below.&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>How/when Flutter runs on iOS and Android</title><link>https://tlduck.com/blog/how-flutter-runs/</link><guid isPermaLink="true">https://tlduck.com/blog/how-flutter-runs/</guid><description>How does Flutter manage to run on iOS and Android?</description><pubDate>Thu, 23 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Being cross-platform, &lt;a href=&quot;https://flutter.dev/&quot;&gt;Flutter&lt;/a&gt; abstracts away Platform APIs, such as iOS, Android, Web, macOS, Windows and Linux. However, there are platform specific differences which will affect how you implement features or develop package plugins.&lt;/p&gt;
&lt;p&gt;For example, on iOS, you need to implement &lt;a href=&quot;https://developer.apple.com/documentation/usernotificationsui/unnotificationcontentextension/1845197-didreceivenotificationresponse&quot;&gt;&lt;code&gt; didReceive(_:completionHandler:)&lt;/code&gt;&lt;/a&gt; in your app&apos;s main entrypoint (AppDelegate), where as on Android, you need to declare a Service or Broadcast Receiver in the &lt;code&gt;AndroidManifest.xml&lt;/code&gt; file, and override &lt;a href=&quot;https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessagingService&quot;&gt;&lt;code&gt;FirebaseMessagingService&lt;/code&gt;&lt;/a&gt; &lt;a href=&quot;https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessagingService#public-void-onmessagereceived-remotemessage-message&quot;&gt;&lt;code&gt;onMessageReceived&lt;/code&gt;&lt;/a&gt; method.&lt;/p&gt;
&lt;h2&gt;Flutter always runs on iOS, but not on Android&lt;/h2&gt;
&lt;h3&gt;iOS&lt;/h3&gt;
&lt;p&gt;On iOS, your Flutter application always runs if a push notification (or background process) is running. This is because the &lt;code&gt;FlutterViewController&lt;/code&gt; is initialized when the application launches, and this creates a FlutterEngine.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;FlutterViewController&lt;/code&gt; is declared in the &lt;code&gt;Main.storyboard&lt;/code&gt; file, so &lt;em&gt;technically&lt;/em&gt; Flutter apps are Storyboard apps (very basic ones) 🤓. A Flutter Engine is created in the Objective-C++ file, &lt;a href=&quot;https://github.com/flutter/engine/blob/master/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm&quot;&gt;&lt;code&gt;FlutterViewController.mm&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auto engine = fml::scoped_nsobject&amp;lt;FlutterEngine&amp;gt;{[[FlutterEngine alloc]
              initWithName:@&quot;io.flutter&quot;
                    project:project
    allowHeadlessExecution:self.engineAllowHeadlessExecution
        restorationEnabled:[self restorationIdentifier] != nil]};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; this isn&apos;t Objective-C, it&apos;s Objective-C++. It has both verbose allocation/initiazation syntax and also uses C++ style class: &lt;a href=&quot;https://github.com/flutter/engine/blob/master/fml/platform/darwin/scoped_nsobject.h&quot;&gt;``&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename NST&amp;gt;
class scoped_nsobject : public scoped_nsprotocol&amp;lt;NST*&amp;gt; {
 public:
  explicit scoped_nsobject(NST* object = nil) : scoped_nsprotocol&amp;lt;NST*&amp;gt;(object) {}

  scoped_nsobject(const scoped_nsobject&amp;lt;NST&amp;gt;&amp;amp; that) : scoped_nsprotocol&amp;lt;NST*&amp;gt;(that) {}

  template &amp;lt;typename NSU&amp;gt;
  scoped_nsobject(const scoped_nsobject&amp;lt;NSU&amp;gt;&amp;amp; that) : scoped_nsprotocol&amp;lt;NST*&amp;gt;(that) {}

  scoped_nsobject&amp;amp; operator=(const scoped_nsobject&amp;lt;NST&amp;gt;&amp;amp; that) {
    scoped_nsprotocol&amp;lt;NST*&amp;gt;::operator=(that);
    return *this;
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Android&lt;/h3&gt;
&lt;p&gt;On Android, only the component (e.g. Activity, Broadcast Receiver, Service) you declared runs, and the Flutter application doesn&apos;t &lt;strong&gt;necessarily&lt;/strong&gt; run. The FlutterEngine is only automatically run if a &lt;code&gt;FlutterActivity&lt;/code&gt; or &lt;code&gt;FlutterFragment&lt;/code&gt; is used. For example, in &lt;code&gt;FlutterActivity&lt;/code&gt;, it creates a &lt;code&gt;FlutterEngine&lt;/code&gt; in the &lt;code&gt;onCreate&lt;/code&gt; method. This means on Android, we need to launch &lt;em&gt;some kind of dart/ flutter code&lt;/em&gt; in the component if the FlutterEngine doesn&apos;t yet exist. There are 2 options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Define. This was described in &lt;a href=&quot;https://stackoverflow.com/a/60296798/7365866&quot;&gt;Mahdi-Malv&apos;s answer on Stack Overflow&lt;/a&gt;. This is also the approach of &lt;a href=&quot;https://pub.dev/packages/firebase_messaging&quot;&gt;firebase_messaging&lt;/a&gt;, a Flutter library for push notifications. Take a look at those if you want to understand the approach. Or, you could:&lt;/li&gt;
&lt;li&gt;Launch the original Flutter application. This is what I chose to do.
You can do this in relatively few lines:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;flutterEngine = new FlutterEngine(context, null);
final MethodChannel methodChannel = new MethodChannel(executor.getBinaryMessenger(), methodChannelName, new StandardMethodCodec(new AblyMessageCodec()));
methodChannel.setMethodCallHandler(this);
// Get and launch the users app isolate manually:
flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());
// Even though lifecycle parameter is @NonNull, the implementation `FlutterEngineConnectionRegistry`
// does not use it, because it was only meant to be exposed for testing framework. See https://github.com/flutter/flutter/issues/90316
flutterEngine.getBroadcastReceiverControlSurface().attachToBroadcastReceiver(receiver, null);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you do this, you&apos;ll &lt;a href=&quot;https://github.com/flutter/flutter/issues/90182#issuecomment-924158973&quot;&gt;need the Flutter application to inform the Android side&lt;/a&gt; that the dart side is ready to receive data being sent the Android side. This is because there is no way for the Android side to determine when the FlutterEngine/Isolate/Flutter app is ready.&lt;/p&gt;
&lt;p&gt;I&apos;ll stop there. Try to understand the following things by reading code, preferably cloning the repo and navigating the code using an IDE (Android Studio, Xcode and AppCode, rather than VSCode for this kind of code):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understand implementation details for launching the Flutter application: Invoking methods between Dart and Platform side, and passing data. Drawing a diagram on paper would help. You can start with &lt;a href=&quot;https://github.com/FirebaseExtended/flutterfire/blob/master/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingBackgroundExecutor.java&quot;&gt;Firebase Messaging&apos;s &lt;code&gt;FlutterFirebaseMessagingBackgroundExecutor.java&lt;/code&gt;&lt;/a&gt;. Then you could take a look at my simpler implementation in &lt;a href=&quot;https://github.com/ably/ably-flutter/pull/166&quot;&gt;ably_flutter&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;How FlutterEngine works internally with the Platform shell: &lt;code&gt;FlutterViewController&lt;/code&gt;, &lt;code&gt;FlutterJNI&lt;/code&gt;, etc. You should clone &lt;a href=&quot;https://github.com/flutter/engine&quot;&gt;&lt;code&gt;flutter/engine&lt;/code&gt;&lt;/a&gt;.
&lt;ul&gt;
&lt;li&gt;On Android, start with: &lt;code&gt;FlutterActivity&lt;/code&gt; and then &lt;code&gt;FlutterActivityAndFragmentDelegate&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On iOS, start with &lt;code&gt;FlutterViewController.mm&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Feel free to ask questions in the comments below :)&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>SSH into your private machines from anywhere, for free, using Cloudflare Tunnel</title><link>https://tlduck.com/blog/ssh-over-cloudflare/</link><guid isPermaLink="true">https://tlduck.com/blog/ssh-over-cloudflare/</guid><description>SSH to machines in private networks using an SSH key, from anywhere in the world, using Cloudflare Tunnel.</description><pubDate>Thu, 10 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Outcome&lt;/h2&gt;
&lt;p&gt;By the end of this post, you&apos;ll be able to run: &lt;code&gt;ssh $machine_name&lt;/code&gt; from anywhere in the internet-connected planet, using SSH keys. It is free and requires no future maintainance. This guide uses &lt;a href=&quot;https://www.cloudflare.com/en-gb/products/tunnel/&quot;&gt;Cloudflare Tunnel&lt;/a&gt;, a service by Cloudflare with a free-tier. It will filter traffic to your machines through Cloudflare&apos;s network, including authenticating you. Because of this, your machines won&apos;t directly be exposed to threat actors and &quot;1337 haxors&quot;.&lt;/p&gt;
&lt;p&gt;This was &lt;a href=&quot;https://news.ycombinator.com/item?id=30283987&quot;&gt;discussed on Hacker News&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;You might have a machine running in your local network and want to access from anywhere in the world. &lt;strong&gt;Alternative&lt;/strong&gt; solutions include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;configuring your router to &lt;strong&gt;port forward&lt;/strong&gt; to your specific machine or setting your machine as a demilitarized zone (DMZ). &lt;strong&gt;Disadvantages:&lt;/strong&gt; your home router&apos;s IP address might change, so you&apos;ll need to use the new IP address. Each port is also limited to a single machine, so you&apos;d have to choose a different port for a different machine. Your DNS record also publishes the IP address of your router, which can be used to locate you - &lt;em&gt;somewhat unreliably, depending on who you are&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Many alternatives are listed in &lt;a href=&quot;https://github.com/anderspitman/awesome-tunneling&quot;&gt;anderspitman/awesome-tunnelling&lt;/a&gt;, including &lt;a href=&quot;https://www.cloudflare.com/en-gb/products/tunnel/&quot;&gt;Cloudflare Tunnel&lt;/a&gt;. Many of these require configuration, and the last time I tried ngrok, I couldn&apos;t choose the domain name on the free tier.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I already use Cloudflare for this website (Cloudflare Pages), web analytics, configuring DNS records, and registering my domains (with no additional fee on top of the Administrator fee, e.g. Verisign for &lt;code&gt;.com&lt;/code&gt; and Nominet for &lt;code&gt;.uk&lt;/code&gt;). Hey UK domain owners, Cloudflare Registrar supports &lt;code&gt;uk&lt;/code&gt; and &lt;code&gt;co.uk&lt;/code&gt; TLDs now 😉.&lt;/p&gt;
&lt;p&gt;So I used &lt;code&gt;cloudflared&lt;/code&gt; to do this, but realised there was no guide written that takes me step by step, to success. There was a &lt;a href=&quot;https://developers.cloudflare.com/cloudflare-one/tutorials/ssh&quot;&gt;Cloudflare official guide&lt;/a&gt;, but some of the steps are not necessary, confusing (e.g. limiting the &quot;application&quot; to 1 month) and missing the &lt;code&gt;config.yml&lt;/code&gt; schema.&lt;/p&gt;
&lt;h2&gt;Pre-requisites 🦑&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A free Cloudflare account&lt;/li&gt;
&lt;li&gt;Understand Cloudflare Tunnel, by following &lt;a href=&quot;https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide&quot;&gt;Set up your first tunnel&lt;/a&gt;. You&apos;ll end up with a website added to Cloudflare, &lt;code&gt;cloudflared&lt;/code&gt; &lt;a href=&quot;https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation&quot;&gt;installed&lt;/a&gt; and logged in on your machine, and a high level understanding of Cloudflare Tunnel. &lt;strong&gt;You don&apos;t have to do step 4 (create configuration file) or later.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Setup SSH server on your target machine 🎯&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Skip if you&apos;ve done this already, for example, you can already SSH into your machine.&lt;/li&gt;
&lt;li&gt;For Ubuntu/Debian,
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Install service&lt;/strong&gt;: run &lt;code&gt;sudo apt install openssh-server&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Start service&lt;/strong&gt;: run &lt;code&gt;sudo systemctl start ssh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schedule service&lt;/strong&gt; (to start on machine start-up): &lt;code&gt;sudo systemctl enable ssh&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;For other operating systems, try using search engines.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Quick check: setup a tunnel and SSH 🧪&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;This step is just a quick check that you can SSH into the machine, before you waste time configuring stuff.&lt;/li&gt;
&lt;li&gt;Start a cloudflare tunnel: run &lt;code&gt;cloudflared tunnel --hostname machine.example.com --url ssh://localhost:22&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Reminder: edit &lt;code&gt;example.com&lt;/code&gt; to one you have added on Cloudflare, and update &lt;code&gt;machine&lt;/code&gt; to whatever you prefer. &lt;code&gt;cloudflared&lt;/code&gt; will create a CNAME record called &lt;code&gt;machine.example.com&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SSH into your machine using password based authentication: run &lt;code&gt;ssh username@machine.example.com&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Reminder: Update username and host.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Configure the &lt;code&gt;cloudflared&lt;/code&gt; service 🧑‍💻&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;We want this service to be running all the time (&lt;em&gt;daemon&lt;/em&gt;), not just when I run the command. Also, some of the steps in &lt;a href=&quot;https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/run-as-service&quot;&gt;Cloudflare: Run as a service&lt;/a&gt; are broken.&lt;/li&gt;
&lt;li&gt;Create a tunnel: &lt;code&gt;cloudflared tunnel create $tunnel_name&lt;/code&gt;, where &lt;code&gt;$tunnel_name&lt;/code&gt; is a name you can use to reference it.&lt;/li&gt;
&lt;li&gt;Update a DNS record: &lt;code&gt;cloudflared tunnel route dns $tunnel_id phanteks.orth.uk&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create a config file: &lt;code&gt;~/.cloudflare/config.yml&lt;/code&gt;, containing:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;tunnel: $tunnel_id
credentials-file: /home/$user/.cloudflared/$tunnel_id.json
url: ssh://localhost:22
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Run the tunnel: &lt;code&gt;cloudflared tunnel run --url=ssh://localhost:22 $tunnel_name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Install service: &lt;code&gt;sudo cloudflared --config /home/ben/.cloudflared/config.yml service install&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Start service: run &lt;code&gt;sudo systemctl start cloudflared&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Schedule service (to start on machine start-up): &lt;code&gt;sudo systemctl enable cloudflared&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Configure SSH client 💻&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Create a keypair. The private key is for your client and the public key should be given to anyone who wants to authenticate you (the machine you want to SSH to, &lt;em&gt;Phanteks&lt;/em&gt;). My keypair is called &lt;code&gt;phanteks&lt;/code&gt;(private key) and &lt;code&gt;phanteks.pub&lt;/code&gt; (public key):
&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;ssh-keygen&lt;/code&gt;. Before that, preferably &lt;code&gt;cd ~/.ssh&lt;/code&gt;, so that your generated keys are stored there.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Copy over the &lt;strong&gt;public&lt;/strong&gt; SSH key (&lt;code&gt;$KEYNAME.pub&lt;/code&gt;): Run &lt;code&gt;ssh-copy-id -i ~/.ssh/KEYNAME.pub $machine_domain&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add the following to &lt;code&gt;~/.ssh/config&lt;/code&gt;. Create the file if it doesn&apos;t exist.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# This only works when you are on the same local network
Host phanteksLocal
        HostName Phanteks.local
        IdentityFile ~/.ssh/phanteks
        User ben
        Port 22

# Cloudflare Tunnel
Host phanteks
        HostName phanteks.orth.uk
        IdentityFile ~/.ssh/phanteks
        ProxyCommand cloudflared access ssh --hostname %h
        User ben
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Secure SSH server 🚔&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Optional: Change the port used by SSH, since the default port is well known (22). You&apos;ll also have to update &lt;code&gt;~/.ssh/config&lt;/code&gt; and &lt;code&gt;~/.cloudflared/config.yml&lt;/code&gt; with this new port.
&lt;ul&gt;
&lt;li&gt;Warning ‼️: &lt;code&gt;cloudflared&lt;/code&gt; actually duplicates this file into &lt;code&gt;/etc/cloudflared/config.yml&lt;/code&gt; first when you install the service. If you change &lt;code&gt;~/.cloudflared/config.yml&lt;/code&gt;, be sure to:
&lt;ul&gt;
&lt;li&gt;manually update the file, or&lt;/li&gt;
&lt;li&gt;delete the file (run &lt;code&gt;sudo rm /etc/cloudflared/config.yml&lt;/code&gt;) and reinstall the service (run &lt;code&gt;sudo cloudflared --config /home/$USER/.cloudflared/config.yml service install&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Optional: Disable password authentication
&lt;ul&gt;
&lt;li&gt;This is usually done to improve security: mitigate random password attacks, but this is already mitigated with Cloudflare Tunnels.&lt;/li&gt;
&lt;li&gt;But in the spirit of &quot;defense in depth&quot;, we should still disable it. We should assume attackers might still come from within your local network, including successfully authenticating with Cloudflare Tunnel&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Restart the services: run &lt;code&gt;sudo systemctl restart ssh cloudflared&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Usage: connect to machine 🤞&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;ssh phanteks&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion 💌&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Maybe there is a way to automate this so all machines I use in the future will be set up automatically. Thankfully, I am not building a server farm at home or moving homes very often, so this is a very low priority. I was just taking a quick look at Ansible yesterday.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Question:&lt;/strong&gt; do you use a different tool which require no maintenance or cost to run?&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Playing Android device audio on macOS</title><link>https://tlduck.com/blog/android-macOS-sound-relay/</link><guid isPermaLink="true">https://tlduck.com/blog/android-macOS-sound-relay/</guid><description>Instructions on relaying music and sound from Android to your macOS</description><pubDate>Sat, 11 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Why?&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Update:&lt;/em&gt; scrcpy 2.0+ supports Audio forwarding. You don&apos;t need &lt;code&gt;sndcpy&lt;/code&gt; anymore. Thanks &lt;a href=&quot;https://github.com/rom1v&quot;&gt;Romain Vimont&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I want to listen to the instructor playing on an Android app (&lt;a href=&quot;https://fiit.tv/&quot;&gt;Fiit&lt;/a&gt;) on my speakers which are connected to my MacBook. I am already sharing the screen of my Android on my MacBook by using &lt;a href=&quot;https://github.com/Genymobile/scrcpy&quot;&gt;&lt;code&gt;scrcpy&lt;/code&gt;&lt;/a&gt;, a tool by Romain Vimont. &lt;a href=&quot;https://github.com/rom1v/sndcpy&quot;&gt;&lt;code&gt;sndcpy&lt;/code&gt;&lt;/a&gt; lets us to do this, also a tool made by Romain Vimont. The naming of these tools were inspired by C library function, &lt;a href=&quot;https://www.cplusplus.com/reference/cstring/strcpy/&quot;&gt;&lt;code&gt;strcpy&lt;/code&gt;&lt;/a&gt;, which copies strings between memory locations.&lt;/p&gt;
&lt;p&gt;You want to play music or app sounds on your Android device on your macOS, and&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You are displaying your Android screen on the mac already, but you want sound too&lt;/li&gt;
&lt;li&gt;Your Android device doesn&apos;t have an audio jack&lt;/li&gt;
&lt;li&gt;Your speakers aren&apos;t bluetooth&lt;/li&gt;
&lt;li&gt;You don&apos;t want to use your headphones
&lt;ul&gt;
&lt;li&gt;I do not want to sweat onto my headphones, or&lt;/li&gt;
&lt;li&gt;I want to share my music with others&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Your macOS is connected via AUX cable (or USB-C/HDMI) to speakers, so &lt;strong&gt;let&apos;s use them instead&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Pre-requisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Command line experience, or just being resourceful.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;adb&lt;/code&gt; installed and usable. To install, run &lt;code&gt;brew install android-platform-tools&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;VLC installed in your &lt;code&gt;/Applications&lt;/code&gt; folder: https://www.videolan.org/vlc/&lt;/li&gt;
&lt;li&gt;Open Terminal.app (already installed at &lt;code&gt;/Applications/Utilities.Terminal.app&lt;/code&gt;) or &lt;a href=&quot;https://iterm2.com/&quot;&gt;iterm2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Installation steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Download the sndcpy APK and script from &lt;a href=&quot;https://github.com/rom1v/sndcpy&quot;&gt;rom1v/sndcpy on GitHub&lt;/a&gt;, and place them in &lt;code&gt;/usr/local/bin&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unzip the file downloaded from Github and move them using
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo mv ~/Downloads/sndcpy/sndcpy /usr/local/bin&lt;/code&gt;, and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo mv ~/Downloads/sndcpy/sndcpy.apk /usr/local/bin&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;This APK is installed to your android device whenever you run &lt;code&gt;sndcpy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a script called &lt;code&gt;vlc&lt;/code&gt; in &lt;code&gt;/usr/local/bin&lt;/code&gt; by running &lt;code&gt;sudo vim /usr/local/bin/vlc&lt;/code&gt;, and passing the following content:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;/Applications/VLC.app/Contents/MacOS/VLC -I rc &quot;$@&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The reason we put it in &lt;code&gt;/usr/local/bin&lt;/code&gt; is that your mac will instantly be able to use it like a command, by calling &lt;code&gt;vlc&lt;/code&gt; (once you restart the shell 🐚).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optional: Confirm the file contains those contents, by running &lt;code&gt;cat /usr/local/bin/vlc&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make the file executable to you and the group: &lt;code&gt;sudo chmod 755 /usr/local/bin/vlc&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following to your &lt;code&gt;~/.zshrc&lt;/code&gt; so that &lt;code&gt;sndcpy&lt;/code&gt; can get the APK whenever it wants to install it onto your device:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;pre&gt;&lt;code&gt;export SNDCPY_APK=/usr/local/bin/sndcpy.apk
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restart your shell, or reload that file: &lt;code&gt;source ~/.zshrc&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Usage steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Connect android device to computer (or attach it remotely via adb)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;sndcpy&lt;/code&gt; in terminal&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Syncing Obsidian Vault on macOS and Android, for free, using git</title><link>https://tlduck.com/blog/obsidian-sync/</link><guid isPermaLink="true">https://tlduck.com/blog/obsidian-sync/</guid><description>How to sync your Obsidian Vault on macOS and Android, for free, using git</description><pubDate>Mon, 18 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Motivation 💪&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You have been using &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt; to store your notes. I highly recommend it if you don&apos;t - in that case, come back to this page later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Your current solution to write notes is to use a separate app or separate Obsidian Vault on your phone. After you reach your computer, you manually copy and paste data into your primary Obsidian Vault. I previously used Google Keep for temporary notes, which were moved into Obsidian Vault when I found the time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; I no longer use git to sync my Obsidian Vault. This is because manually resolving conflicts between my notes and &lt;code&gt;.obsidian&lt;/code&gt; files using git and Termux is tedious on a small screen and a touch keyboard. Now, I pay for Obsidian Sync. I am planning on trying &lt;a href=&quot;https://syncthing.net/&quot;&gt;Syncthing&lt;/a&gt; soon. I also now trust GitHub (Microsoft) less with my private notes. See &lt;a href=&quot;https://news.ycombinator.com/item?id=27710287&quot;&gt;Copilot regurgitating Quake code, including sweary comments&lt;/a&gt; for some discussion.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Warning 🌶&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;This guide requires the use of &lt;strong&gt;command line&lt;/strong&gt; and &lt;strong&gt;git&lt;/strong&gt;, and storing your data &lt;em&gt;privately&lt;/em&gt; online, like on GitHub. Therefore, it is not end-to-end encrypted, and Github developers could have access to your data.&lt;/li&gt;
&lt;li&gt;If you are unwilling, there are other ways to sync Obsidian Vaults, e.g. using Google Drive, Dropbox or paying for the Sync Service from Obsidian directly.&lt;/li&gt;
&lt;li&gt;If you want end-to-end encryption for your files, use the Obsidian Sync Add-On Service (&lt;a href=&quot;https://obsidian.md/pricing&quot;&gt;currently $8/mo&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Brief notes about my note taking 📝&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.markdownguide.org/&quot;&gt;Markdown&lt;/a&gt; is an open file format to store your information, and it was created by &lt;a href=&quot;https://en.wikipedia.org/wiki/Aaron_Swartz&quot;&gt;Aaron Swartz&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/John_Gruber&quot;&gt;John Gruber&lt;/a&gt;.
&lt;ul&gt;
&lt;li&gt;It&apos;s readable even in its raw format (text) without an app, eg (MS Word), but you can make it look even better with apps like Typora and Obsidian.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open file format:&lt;/strong&gt; This means if the app you&apos;re using shuts down, changes it&apos;s pricing or is unethical, you can take your data to another app without much cost.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;I started using Typora when it was free, and it was great. As they previously warned, it became paid software, and that is fair - it&apos;s a great app.&lt;/li&gt;
&lt;li&gt;I found Obsidian on a variety of websites and from a colleague. Over time, I realised it would organise all the notes I made in the past, and make them searchable.&lt;/li&gt;
&lt;li&gt;Eventually, I abandoned Typora to use Obsidian completely since I could do everything in Obsidian.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Example 😃&lt;/h2&gt;
&lt;p&gt;Here&apos;s a picture of a file from my Obsidian Vault I wrote on macOS, but it&apos;s viewable and editable from Android. My Android device screen is captured using &lt;a href=&quot;https://github.com/Genymobile/scrcpy&quot;&gt;scrcpy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./jetson_notes.png&quot; alt=&quot;android screenshot of Obsidian app&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Steps 🗺&lt;/h2&gt;
&lt;h3&gt;Put your notes in a &lt;code&gt;git&lt;/code&gt; repo hosted online. 🌎&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Basically, use Github, Gitlab or other cloud provider which hosts git repositories.&lt;/li&gt;
&lt;li&gt;You probably want a private repo so that other people can&apos;t read your files.&lt;/li&gt;
&lt;li&gt;Github has a guide for this: &lt;a href=&quot;https://docs.github.com/en/get-started/quickstart/create-a-repo&quot;&gt;Create a repo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Clone the repo on your android phone 📲&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Install GitJournal from the Play Store. &lt;strong&gt;GitJournal&lt;/strong&gt; is an app that allows cloning repos, and syncing files back to the cloud.&lt;/li&gt;
&lt;li&gt;Configure GitJournal to store repo files in the local directory, not in the internal app data:
&lt;ul&gt;
&lt;li&gt;Open GitJournal, Press Hamburger button &amp;gt; Settings &amp;gt; Storage &amp;amp; File Formats &amp;gt; Enable &quot;Store Repo Externally&quot;&lt;/li&gt;
&lt;li&gt;Create a new folder (e.g. called &lt;code&gt;repos&lt;/code&gt;). I chose &lt;code&gt;Documents/repos/&lt;/code&gt;. Then, select the &lt;code&gt;repos&lt;/code&gt; folder to the be the directory. GitJournal will store all the repos, 1 folder each.
&lt;ul&gt;
&lt;li&gt;Some notes about paths:
&lt;ul&gt;
&lt;li&gt;GitJournal will show the true path for the directory (ie &lt;code&gt;/storage/emulated/0/Documents&lt;/code&gt;), whereas&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;Files&lt;/code&gt; app only shows files inside of &lt;code&gt;/storage/emulated/0&lt;/code&gt; as the root aka. Internal storage folder.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Warning 🌶:&lt;/strong&gt; You might get some permissions issues if you did not create the folder yourself, or you are using a restricted directory. In that case, GitJournal will show an error related to permissions. Delete the previous folder (using the &lt;code&gt;Files&lt;/code&gt; app) and find/create a different folder.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Open the Obsidian Vault 📖&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Install &lt;a href=&quot;https://play.google.com/store/apps/details?id=md.obsidian&quot;&gt;Obisidian from the playstore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Open vault in Obsidian&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Usage 🔁&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Make changes to your Vault in Obsidian&lt;/li&gt;
&lt;li&gt;sync with the Cloud using GitJournal&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Ending 👋&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I wrote this document (you are reading it) on Obsidian, and in fact, Obsidian&apos;s new WYSIWIG editor is what inspired me to write &lt;a href=&quot;https://news.ycombinator.com/item?id=30283987&quot;&gt;SSH into your private machines from anywhere, for free, using Cloudflare Tunnel&lt;/a&gt; I posted on HN earlier this year. With the extensions installed, my Obsidian looks like:
&lt;img src=&quot;./writing.png&quot; alt=&quot;obsidian editor screenshot on macOS&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Let&apos;s chat in the comments:&lt;/strong&gt; Was I not clear on some of the steps? Do you have any ideas or suggestions on improving my Obsidian workflow?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Alternatives:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I also tried Termux and cloned my repo there. However, I stopped using it because it didn&apos;t have an easy button to press, unlike the sync button on GitJournal. You have to a run a few commands in the terminal to set up the file permissions and update the package repositories so you can install &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;vim&lt;/code&gt; and other useful tools.&lt;/li&gt;
&lt;li&gt;If you install &lt;code&gt;adb&lt;/code&gt;, you can enter the command line on the Android by running &lt;code&gt;adb shell&lt;/code&gt;. You can explore the Android filesystem and use &lt;code&gt;adb&lt;/code&gt; to manually copy files using &lt;code&gt;scp&lt;/code&gt; or &lt;code&gt;rsync&lt;/code&gt; from your machine.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Not for iOS:&lt;/strong&gt; This guide doesn&apos;t work for iOS, but there is &lt;a href=&quot;https://forum.obsidian.md/t/mobile-setting-up-ios-git-based-syncing-with-mobile-app-using-working-copy/16499&quot;&gt;a way to use git to sync files on iOS&lt;/a&gt;, but the software is paid (Working Copy, $19.99) if you want to make changes on iOS.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Extra Hint: git conflicts&lt;/h2&gt;
&lt;p&gt;If you do use git to sync files, you might find some merge conflicts between devices caused by the &lt;code&gt;.obsidian/workspace&lt;/code&gt; file.
To fix this, in your Obsidian Vault, create a &lt;code&gt;.gitignore&lt;/code&gt; file and add &lt;code&gt;.obisidian/workspace&lt;/code&gt; in the file.
If you have already previously committed the file, run &lt;code&gt;git rm --cached .obsidian/workspace&lt;/code&gt; to untrack the file.&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Replacing SSH over Cloudflare with Tailscale</title><link>https://tlduck.com/blog/tailscale-ssh/</link><guid isPermaLink="true">https://tlduck.com/blog/tailscale-ssh/</guid><description>I originally used Cloudflare Tunnel to SSH into machines at home from other networks, but Tailscale has a much easier product to use.</description><pubDate>Tue, 05 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I wrote &lt;a href=&quot;/ssh-over-cloudflare&quot;&gt;SSH into your private machines from anywhere, for free, using Cloudflare Tunnel&lt;/a&gt; in April which led to a lot of discussion on &lt;a href=&quot;https://news.ycombinator.com/item?id=30283987&quot;&gt;Hacker News&lt;/a&gt;. A lot of people started mentioning alternatives (Tailscale, ZeroTier and issues with Cloudflare).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you like this, you’re gonna love Tailscale https://tailscale.com/ - &lt;strong&gt;&lt;em&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=30284777&quot;&gt;mmettler&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Tailscale has a beta product, &lt;a href=&quot;https://tailscale.com/tailscale-ssh/&quot;&gt;Tailscale SSH&lt;/a&gt; with 2 significant differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Easier setup:&lt;/strong&gt; Tailscale SSH is easy to configure (I don&apos;t really need to write the setup steps).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Less security&lt;/strong&gt;: Cloudflare Tunnel didn&apos;t replace authentication, but Tailscale SSH does. This means Tailscale could SSH (and access files) into my machine if they wanted to, but Cloudflare can&apos;t. With Cloudflare, I still had to authenticate with my private key.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Steps 🛼&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tailscale.com/downloads&quot;&gt;Install tailscale&lt;/a&gt; on client machine (e.g. macOS) and login&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tailscale.com/download/linux&quot;&gt;Install tailscale&lt;/a&gt; on SSH server (e.g. raspberry pi)&lt;/li&gt;
&lt;li&gt;On SSH server, set up Tailscale to use SSH: &lt;code&gt;sudo tailscale up --ssh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Get the IP address of the SSH server machine from client machine or Tailscale website&lt;/li&gt;
&lt;li&gt;SSH into the machine:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ssh username@$IP_ADDRESS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If you have &lt;strong&gt;Cloudflare WARP&lt;/strong&gt;: You need to &lt;strong&gt;disable&lt;/strong&gt; it, because that prevented me connecting to my local machines using Tailscale - even though I could still use e.g. &lt;code&gt;ssh zen@raspberrypi.local -i ~/.ssh/raspberrypi&lt;/code&gt; though.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optional:&lt;/strong&gt; shorten SSH command into &lt;code&gt;ssh pi&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;On Tailscale&apos;s &lt;a href=&quot;https://login.tailscale.com/admin/dns&quot;&gt;DNS admin page&lt;/a&gt;,
&lt;ul&gt;
&lt;li&gt;select a global nameserver and&lt;/li&gt;
&lt;li&gt;enable MagicDNS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;dns_page.png&quot; alt=&quot;DNS admin page&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To SSH into your machine using fewer keystrokes, e.g. &lt;code&gt;ssh pi&lt;/code&gt;, go into Tailscale&apos;s &lt;a href=&quot;https://login.tailscale.com/admin/machines&quot;&gt;Machines admin page&lt;/a&gt;, and &quot;edit machine name&quot;. Disable &quot;auto-generate from OS hostname&quot; and set the machine name.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;machine-name.png&quot; alt=&quot;Machines admin page&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Benefits 🤙&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;No machine passwords or SSH keys needed. Everything is replaced by your Tailscale account or SSO. I can move between laptops by just installing and logging into Tailscale.&lt;/li&gt;
&lt;li&gt;No SSH configuration needed (e.g. &lt;code&gt;~/.ssh/config&lt;/code&gt; and &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The configuration is so easy, that we can save time and avoid frustration at work. I work at a company with quite a few test devices, and each new person or test device would mean more configuration/setup time. We can:
&lt;ul&gt;
&lt;li&gt;see a list of available test devices on the Tailscale website,&lt;/li&gt;
&lt;li&gt;setup developer machines to access these devices using SSO, and&lt;/li&gt;
&lt;li&gt;setup new test devices with 2 commands (install and launch Tailscale).&lt;/li&gt;
&lt;li&gt;We would do all of that whilst avoiding the poor experience with using VPNs and password managers.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion 🏁&lt;/h2&gt;
&lt;p&gt;Overall, I think I&apos;m going to stick to Tailscale instead of Cloudflare Tunnel for accessing machines on my home network when away.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; what would you do? 😇&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Alternative Cargo Registry authentication workaround</title><link>https://tlduck.com/blog/cargo-registry-authentication/</link><guid isPermaLink="true">https://tlduck.com/blog/cargo-registry-authentication/</guid><description>This is a workaround to authenticating with alternative cargo registries like Artifactory, a feature that is not yet available in Cargo.</description><pubDate>Thu, 22 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is a workaround to authenticating with an &lt;em&gt;alternative&lt;/em&gt; cargo registry like Artifactory, a feature that is not yet available in Cargo.&lt;/p&gt;
&lt;h2&gt;Introduction 👋&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;There has been a &lt;a href=&quot;https://github.com/rust-lang/cargo/pull/10592&quot;&gt;Cargo MR: Implement RFC 3139: alternative registry authentication support&lt;/a&gt;, to add support to Cargo to authenticate with Registries when downloading crates. Currently, Cargo only sends authentication tokens for publish and yank network requests. Unfortunately, this MR has been open quite a while - since April 2022.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workaround:&lt;/strong&gt; This workaround works by injecting a basic authentication token to requests to the specific cargo registry.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Background:&lt;/strong&gt; I noticed a colleague implementing a workaround using &lt;a href=&quot;https://pypi.org/project/proxy.py/&quot;&gt;proxy.py&lt;/a&gt;. However, this tool might be more versatile, so it requires more config for MITM proxying, including generating certificate authorities and programatically mutating config files. I knew of &lt;code&gt;mitmproxy&lt;/code&gt; which could potentially do things a bit more cleanly, and was a bit curious how it would work with Cargo. I also knew this attempt would be more open-source-able.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Pause: easier solution 🐣&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;There is an even easier solution.&lt;/li&gt;
&lt;li&gt;If your crates are not sensitive, you could just disable authentication when downloading crates. There is an option to do this for Jfrog Artifactory repositories (for artifactory admins).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Setup ⚙️&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;These are setup instructions. You only do this once per machine. They are written for macOS so adjust them for other platforms if necessary.&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pre-requisites:&lt;/strong&gt; Rust and Cargo: &lt;code&gt;brew install rust&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.mitmproxy.org/stable/overview-installation/&quot;&gt;Install mitmproxy&lt;/a&gt;: run &lt;code&gt;brew install mitmproxy&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;This installs &lt;code&gt;mitmproxy&lt;/code&gt;, &lt;code&gt;mitmdump&lt;/code&gt; and more.&lt;/li&gt;
&lt;li&gt;Note, you can also &lt;code&gt;python3 -m pip install mitmproxy&lt;/code&gt;, which installs binaries in e.g. &lt;code&gt;/Users/$USER/Library/Python/3.9/lib&lt;/code&gt;. Be sure to add this to your path. For example, in &lt;code&gt;~/.zshrc&lt;/code&gt;, add &lt;code&gt;export PATH=/Users/$USER/Library/Python/3.9/lib:$PATH&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;mitmproxy&lt;/code&gt;, and close it: run &lt;code&gt;mitmproxy&lt;/code&gt; and then quit: &lt;code&gt;Ctrl + C&lt;/code&gt;, type &lt;code&gt;yes&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;This will generate the certificate authority certificates in &lt;code&gt;~/.mitmproxy&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Create a file: &lt;code&gt;add_cargo_auth_header.py&lt;/code&gt; is:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;from mitmproxy import http
import base64
import os

REGISTRY_DOMAIN = &quot;example.jfrog.io&quot;

def request(flow: http.HTTPFlow):
    if flow.request.pretty_host == REGISTRY_DOMAIN:
        # If you prefer hardcoding your email and token in your script. This is okay if you don&apos;t commit your file
        email = &quot;user@example.com&quot;
        token = &quot;replace this with your token&quot;

        # Or if you prefer environment variables.
        email = os.environ[&quot;EMAIL&quot;]
        token = os.environ[&quot;TOKEN&quot;]

        encoded_token = base64.b64encode(f&quot;{email}:{token}&quot;.encode())
        flow.request.headers[&quot;Authorization&quot;] = b&quot;Basic &quot; + encoded_token

    # Optional: Handle more registries here.
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Configure cargo to use your registry and this proxy
&lt;ul&gt;
&lt;li&gt;Adjust your desired config file, to use the &lt;strong&gt;mitmproxy&lt;/strong&gt; certificate authority.
&lt;ul&gt;
&lt;li&gt;To affect a single project, adjust &lt;code&gt;.cargo/config.toml&lt;/code&gt;. For more information, see &lt;a href=&quot;https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure&quot;&gt;The Cargo Book: Configuration&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;For example, update your &lt;code&gt;.cargo/config.yaml&lt;/code&gt; to look like:
&lt;ul&gt;
&lt;li&gt;This example uses an &quot;Jfrog Artifactory&quot; Cargo registry. Be sure to update the cainfo file path&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;[registries.example]
index = https://example.jfrog.io/artifactory/git/cargo-repository.git

[http]
proxy = &quot;localhost:9001&quot;
# on macOS
cainfo = &quot;/Users/ben/.mitmproxy/mitmproxy-ca.pem&quot;
# in a Docker container
# cainfo = &quot;/root/.mitmproxy/mitmproxy-ca.pem&quot;

[net]
git-fetch-with-cli = true
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Authenticate with the Registry index
&lt;ul&gt;
&lt;li&gt;The index contains the information about all crates&lt;/li&gt;
&lt;li&gt;Warning: update &lt;code&gt;EMAIL&lt;/code&gt; and &lt;code&gt;INDEX_DOMAIN&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# Replace @ with %40
EMAIL=user@example.com
REGISTRY_DOMAIN=example.jfrog.io/
SAFE_EMAIL=${EMAIL/@/%40}
git config --global url.&quot;https://${SAFE_EMAIL}:${TOKEN}@/${REGISTRY_DOMAIN}&quot;.insteadOf &quot;https://${REGISTRY_DOMAIN}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Usage 🎮&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Start the local proxy server: either
&lt;ul&gt;
&lt;li&gt;for interactive: run &lt;code&gt;mitmproxy --listen-port 9001 -s add_cargo_auth_header.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;for no interactivity: run &lt;code&gt;mitmdump --listen-port 9001 -s add_cargo_auth_header.py&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;You can replace &lt;code&gt;9001&lt;/code&gt; with whichever port you prefer.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Launch cargo as normal in a separate terminal. e.g. &lt;code&gt;cargo build&lt;/code&gt;, &lt;code&gt;cargo run&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Bonus: Usage in CI 🎁&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;To use it programatically (e.g. in CI), you could run:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# Start proxy server
mitmdump --listen-port 9001 -s add_cargo_auth_header.py &amp;amp;
# Use cargo as normal
cargo build # or cargo update, cargo run, etc.
# Kill proxy server
kill %1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion 🏁&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I look forward to deleting this local proxy server when this feature is available in Cargo. In the mean time, I&apos;ve got a consistent way to access other registries securely.&lt;/li&gt;
&lt;li&gt;If you have a suggestion or found a problem, comment below. That would probably be useful to other readers, and also helps me learn.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Docker Compose, quickly</title><link>https://tlduck.com/blog/docker-compose-quickly/</link><guid isPermaLink="true">https://tlduck.com/blog/docker-compose-quickly/</guid><description>This is a *quick, practical guide* to get you started with using Docker Compose, locally.</description><pubDate>Sat, 17 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is a &lt;em&gt;quick, practical guide&lt;/em&gt; to get you started. A common issue I notice in projects that use &lt;code&gt;Dockerfile&lt;/code&gt;&apos;s is that they don&apos;t easily run on developer machines and don&apos;t work across operating systems and IDEs (Visual Studio Code, vs. PyCharm, vs. Command line).&lt;/p&gt;
&lt;h2&gt;Why Docker? 😋&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consistent environment:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Some binaries and applications might not be able to run your platform natively. Fortunately, Apple ARM macs (e.g. M1/M2) already allow you to run &lt;code&gt;amd64&lt;/code&gt;/&lt;code&gt;x86_64&lt;/code&gt; binaries thanks to Rosetta 2. However, you still can&apos;t run &lt;code&gt;linux/arm64&lt;/code&gt; or &lt;code&gt;linux/amd64&lt;/code&gt; on macOS. You still can&apos;t run ARM binaries on older, &lt;code&gt;x86_64&lt;/code&gt; macs. &lt;strong&gt;With Docker, you can run binaries from other architectures more easily,&lt;/strong&gt; thanks to QEMU emulation.&lt;/li&gt;
&lt;li&gt;Environment variables are not automatically passed to the docker image or container.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clean environment:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;whenever I want a clean environment to test in, I find myself spawning a docker container and entering it&apos;s shell: &lt;code&gt;docker run -it python:3.9-bullseye bash&lt;/code&gt;. This makes me more confident when helping colleagues or writing articles.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Powerful build:&lt;/strong&gt; you can &lt;strong&gt;compile docker images&lt;/strong&gt; for other platforms on your machine. Without Docker, how can you build applications or binaries for Linux?&lt;/li&gt;
&lt;li&gt;Many more benefits. This is a &lt;em&gt;quick&lt;/em&gt; guide. 😉&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Steps 🚶🏻&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Do all the steps in your project directory. See https://github.com/uxduck/docker-compose-guide for an example.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Create a &lt;code&gt;Dockerfile&lt;/code&gt; for your application&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Writing a &lt;code&gt;Dockerfile&lt;/code&gt; is very specific to the application you want to deploy, so I won&apos;t include that here. It can also get quite complex. In the example, we use a basic `Dockerfile.&lt;/li&gt;
&lt;li&gt;You&apos;ll need to copy over your application.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Create a &lt;code&gt;docker-compose.yml&lt;/code&gt; file&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;This should contain:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;services:
  development:
    build:
      context: &quot;./&quot;
      dockerfile: Dockerfile
      args:
        # Optional credentials used during image build.
        - USERNAME
        - TOKEN
    env_file:
      - .env
    command: &quot;tail -f /dev/null&quot;
    # Specify a platform if some of your binaries only work on certain platforms. e.g. linux/amd64, linux/arm64
    # platform: linux/amd64
    volumes:
      - .:/workspace
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;If you need credentials when building your image:&lt;/h3&gt;
&lt;h4&gt;Create a &lt;code&gt;.env&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;.env&lt;/code&gt; file should contain all the credentials in your &lt;code&gt;args&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Add &lt;code&gt;.env&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Ignore this file, because it will contain your credentials.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Create a &lt;code&gt;.env.example&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Copy your &lt;code&gt;.env&lt;/code&gt; and remove the credentials.&lt;/li&gt;
&lt;li&gt;Keep this up to date whenever your &lt;code&gt;.env changes&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Create VSCode devcontainer configuration&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Even if you don&apos;t use VSCode, another developer might want in the future. Create one now so you configure it correctly 😆.
&lt;ul&gt;
&lt;li&gt;We don&apos;t want developer environment to drift or become inconsistent between Visual Studio Code users and others. Unfortunately, &lt;code&gt;devcontainers&lt;/code&gt; defaults to configuring things inside &lt;code&gt;devcontainers.json&lt;/code&gt; this can easily happen.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;install &quot;Remote - Containers&quot; extension from Microsoft.&lt;/li&gt;
&lt;li&gt;Open Command Palette, run &quot;Remote-Container: Add Development Container Configuration Files...&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delete&lt;/strong&gt; &lt;code&gt;devcontainer/docker-compose.yml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Inside &lt;code&gt;.devcontainer/devcontainer.json&lt;/code&gt;,
&lt;ul&gt;
&lt;li&gt;use only 1 docker-compose file:&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;{
    ...
    &quot;dockerComposeFile&quot;: [&quot;../docker-compose.yml&quot;],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Add a warning to the top of &lt;code&gt;.devcontainer/devcontainer.json&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// WARNING: developers should put most configuration in docker-compose.yml instead of VSCode specific configuration to ensure all developer environments work, not just VSCode.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Try launching the application&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;From the command line, run &lt;code&gt;docker-compose up -d --build&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Enter the shell inside the container: &lt;code&gt;docker exec -it docker-compose-guide_development_1 bash&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;This command won&apos;t need to change everything you build the image or restart your computer.&lt;/li&gt;
&lt;li&gt;That name should be consistent to the folder name and the service inside &lt;code&gt;docker-compose.yml&lt;/code&gt;. If you change those, you&apos;ll need to update this command.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Run a command, you&apos;re in Linux: e.g. run &lt;code&gt;uname -a&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;This should return &lt;code&gt;Linux eed70de56bc9 5.10.104-linuxkit #1 SMP PREEMPT aarch64 GNU/Linux&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Write the documentation&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Describe what you need to do to launch the application locally. Take a look at the example &lt;a href=&quot;https://github.com/uxduck/docker-compose-guide#contributing&quot;&gt;Contributing guide&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;More reading 👀&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker images don&apos;t have the Linux kernel in them, they share the one from the Host. Running Docker on macOS is more resource intensive than on a Linux host, because it needs to run a Linux kernel. More information on this &lt;a href=&quot;https://stackoverflow.com/a/55085366/7365866&quot;&gt;Stack Overflow answer&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Every Docker image eventually extends from &lt;code&gt;scratch&lt;/code&gt;. For example, the &lt;a href=&quot;https://github.com/debuerreotype/docker-debian-artifacts/blob/8b767b40652eff693130e3364e8636e1e1cd1e53/bullseye/Dockerfile&quot;&gt;Debian &lt;code&gt;bullseye&lt;/code&gt; image&lt;/a&gt; is actually pretty simple. It does 1 thing: copy and extract the root file system (33.4MB) into the container:
&lt;ul&gt;
&lt;li&gt;The root file system provides &lt;strong&gt;a lot&lt;/strong&gt;, you can download it from &lt;a href=&quot;https://github.com/debuerreotype/docker-debian-artifacts/blob/8b767b40652eff693130e3364e8636e1e1cd1e53/bullseye/Dockerfile&quot;&gt;Github&lt;/a&gt;, extract it and take a look.&lt;/li&gt;
&lt;li&gt;More information on this &lt;a href=&quot;https://stackoverflow.com/a/47374705/7365866&quot;&gt;Stack Overflow answer&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;FROM scratch
ADD rootfs.tar.xz /
CMD [&quot;bash&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Useful images:&lt;/strong&gt; If you need &lt;strong&gt;more functionality in your Docker image&lt;/strong&gt;, &lt;a href=&quot;https://docs.docker.com/develop/develop-images/dockerfile_best-practices/&quot;&gt;you could write &lt;code&gt;Dockerfiles&lt;/code&gt;&lt;/a&gt;. Try to find &lt;a href=&quot;https://hub.docker.com/&quot;&gt;existing Docker images&lt;/a&gt; that do most of your needs, and extend from there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lightweight images:&lt;/strong&gt; If you need to &lt;strong&gt;deploy a lightweight version of your image&lt;/strong&gt;, you could build &lt;strong&gt;multi-stage docker files&lt;/strong&gt;. These are slower, but can be better for deployment because they smaller and more locked down. For this reason, I like to provide 2 &lt;code&gt;Dockerfile&lt;/code&gt;&apos;s, one for release, and one for local development. Backstage, by Spotify does something similar in &lt;a href=&quot;https://backstage.io/docs/deployment/docker&quot;&gt;Building a Docker image&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deployment:&lt;/strong&gt; If you need to deploy your application to a &lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt; cluster, you could look at &lt;a href=&quot;https://helm.sh/&quot;&gt;Helm&lt;/a&gt;. If you want even more advanced deployments, take a look at &lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/&quot;&gt;ArgoCD&lt;/a&gt;.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Warning:&lt;/strong&gt; Please don&apos;t go into the wormhole of using all the tools available to you today, as it will add the complexity of your project. Use it when you need it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion 🏁&lt;/h2&gt;
&lt;p&gt;That was a quick guide to using Docker compose locally. I&apos;ll be happy to clarify things and point to more resources. Some tricky things for me were to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;setup Gitlab authenticate with a private Container Registry so that it would use the image when running CI commands. You need to generate a &lt;code&gt;DOCKER_AUTH_CONFIG&lt;/code&gt; Gitlab environment variable.&lt;/li&gt;
&lt;li&gt;Building complex existing tools together with my application (e.g. Ardupilot simulator/SITL) in the same Docker image. This is useful for running integration tests of your application communicating with the drone simulator.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Why you shouldn&apos;t pay into a pension 💀</title><link>https://tlduck.com/blog/rethink-your-pension/</link><guid isPermaLink="true">https://tlduck.com/blog/rethink-your-pension/</guid><description>My principle for a few years now, is to never use a pension.</description><pubDate>Sun, 19 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve been opting out of my pension within a few months of starting a job at any company. Every time, it&apos;s taken considerable thought and confusion. It seems like every few months a &lt;em&gt;ridiculous&lt;/em&gt; event occurs, related to pensions. I wanted to share why I stopped contributing to pensions so others can make a more informed decision. I wish I had seen such an article in the past.&lt;/p&gt;
&lt;h2&gt;Caveat 🥦&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The following is based on my experience of having 3 pensions in the UK (different companies I worked for use different providers).&lt;/li&gt;
&lt;li&gt;I am not selling anything.&lt;/li&gt;
&lt;li&gt;This content is free.&lt;/li&gt;
&lt;li&gt;This is not financial advice, which is a regulated activity. Financial advice is usually paid for, and the seller of the advice often makes money from you even when you don&apos;t - their incentives are not aligned with yours.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Background: What is a pension? 🤯&lt;/h2&gt;
&lt;p&gt;A pension is a monthly bill you automatically pay as a percentage of your salary. For example, if you earn £1000 a month, you can pay £50 (5%) every month until you retire. This money cannot be accessed until you retire. It is opt-in, which means everyone who works gets this, &lt;strong&gt;by default&lt;/strong&gt;. In some cases, your employer will pay more money every month, e.g. £25.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ChatGPT:&lt;/strong&gt; A pension is a long-term savings plan, where you contribute a percentage of your salary every month until you retire. These contributions are usually made automatically, and you cannot access the money until you reach retirement age.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Why use a pension 🧟‍♂️&lt;/h2&gt;
&lt;p&gt;There&apos;s a lot of information about this online. For example, a course by MoneySavingExpert, &lt;a href=&quot;https://www.open.edu/openlearn/mod/oucontent/view.php?id=100871&quot;&gt;MSE’s Academy of Money&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Why avoid pensions 🚨&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Potential death:&lt;/strong&gt; You might not make it to the end. You might die before you retire. And if you do make it to retirement, the pension fund has an incentive to make it harder for you to get your money - they might not even have all your money. They might lobby to change the date, reduce the payments, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Law changes:&lt;/strong&gt; The government and pension providers have a lot of power over your money. They can raise the age you can take your money or limit how you can use that money. See &lt;a href=&quot;https://www.gov.uk/government/news/proposed-new-timetable-for-state-pension-age-increases&quot;&gt;Proposed new timetable for State Pension age increases&lt;/a&gt;. Also, the government organisation to handle pension complaints is not &lt;a href=&quot;https://uk.trustpilot.com/review/www.pensions-ombudsman.org.uk&quot;&gt;well trusted on trustpilot&lt;/a&gt; - currently rated 1.7 stars. Your retirement plans will be affected by the governments changes. As governments get squeezed - they need to squeeze their people, with tax and pensions law changes.
&lt;ul&gt;
&lt;li&gt;Examples:
&lt;ul&gt;
&lt;li&gt;UK: Jeremy Hunt plans to tax pensioners in &lt;a href=&quot;https://www.reddit.com/r/unitedkingdom/comments/ynlr7l/jeremy_hunt_plots_10bn_tax_grab_from_the_better/&quot;&gt;November 2022&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.theguardian.com/world/2023/mar/19/france-government-macron-no-confidence-vote-pension-age-rise&quot;&gt;France: Macron pushing through pension changes without vote&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Higher taxes and extra cost:&lt;/strong&gt; Although you don&apos;t get taxed now, you will get taxed when you retire. By then, taxation will probably be higher, not lower, as governments go into more debt. Overall, you might get taxed more than just getting the money now. See &lt;a href=&quot;https://www.statista.com/chart/24330/uk-tax-burden-as-share-gdp-timeline/&quot;&gt;UK tax burden to hit highest levels since the 60s&lt;/a&gt;. Also, the pension providers charge you a fee (percentage of your pot).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Get the money now:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Contribute to a house deposit&lt;/li&gt;
&lt;li&gt;Invest in yourself: education, training, holidays?&lt;/li&gt;
&lt;li&gt;Feed people: bills have been going up, and perhaps you actually want to spend that money now to feed your children today.&lt;/li&gt;
&lt;li&gt;Invest your money anywhere else&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rigidity:&lt;/strong&gt; currency might be devalued in the future. Sure, you can invest in index or mutual funds on there instead, but have you seen the limitations of the pension websites.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Irrelevance:&lt;/strong&gt; for some people who are financially well-off, they would have investments which make them more money, consistently. This might be a business, or a mortgage on a flat they own but rent out. Every month, they get more money than they spend (including mortgage, bills, food, holidays). The cost of managing multiple pensions would be unnecessary work in their lives. They&apos;d have to monitor a separate app and potentially types of investments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Incentives and competition:&lt;/strong&gt; pension funds/managers are not very good at investing: the UI is not very good, unlike other dedicated-investment apps. They have no incentive to build good UX, since you have no choice in taking your money out. They are not very good companies, just look on the complaints filed on the pensions ombudsman (for example, this &lt;a href=&quot;https://www.pensions-ombudsman.org.uk/decision/2022/cas-56722-r9w8/nhs-pension-scheme-cas-56722-r9w8&quot;&gt;complaint on the NHS Pension Scheme&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fragmentation of pension:&lt;/strong&gt; over the years, you might have opened more than 1 pension. It hurts my brain to remember the pension providers that I might have used.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Switching cost:&lt;/strong&gt; Choosing and moving-between them is a hassle. Different jobs you have will get different pension providers. There are many pension providers, and they do not want to talk nicely with each other. See trustpilot reviews of nestpensions.
&lt;ul&gt;
&lt;li&gt;The company has no incentive to be a good investor. They have your money already, and have the ultimate customer on long term subscription.&lt;/li&gt;
&lt;li&gt;Can you even move your pension to another country? What happens if you don&apos;t like Brexit or are struggling in the UK economy?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forget:&lt;/strong&gt; You might forget about some pensions. The company has no incentive to remind you.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Less control:&lt;/strong&gt; You have very little control over the money:
&lt;ul&gt;
&lt;li&gt;simply, you can&apos;t use the money until the term ends (48 years for me). Your buying power is less. When you find a flat to buy and it is 5% higher than you can buy, remember your pension took that money away from you.&lt;/li&gt;
&lt;li&gt;you can&apos;t control how it is invested. If you bought that 5% house, you could rent out some room to lodgers, and save money.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Philosophical:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It is opt-out:&lt;/strong&gt; Facebook was annoyed that the iOS default switched away from opt-out to &quot;ask the user&quot; (not even as harsh as opt-in), they even put adverts on the news to target Apple. They set it to opt-out so most people are automatically in it - if it&apos;s so good, why not just let people know and let them opt-in.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The wrong solution:&lt;/strong&gt; What is the argument for a pension? That you don&apos;t save enough for retirement, you overspend before retirement and/or you overspend when you&apos;re retired. Surely a good solution would be education.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agility:&lt;/strong&gt; Those who move fast can exploit an opportunity for success. Conversely, if you move slowly, you have a lot to lose. Pensions is immobile money, which means you get the worst deals.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Summary 🏁&lt;/h2&gt;
&lt;p&gt;So that&apos;s it - an uncommon view on pensions. Reply with your view points in the comment section - tell me I&apos;m nuts - you might be helping me 🤓. I wonder how the recent and future AI technology will affect the economy and governments, which in turn affect taxes and pensions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; Do not opt-out of the pension to spend the extra money on &lt;em&gt;unnecessary things&lt;/em&gt;. &lt;strong&gt;Default&lt;/strong&gt; to saving money, not spending it. When I got my first raise, I thought to myself, &quot;Oh wow, I&apos;ve got £N extra cash, every month. What can I spend it on?&quot;. Then I remembered, this will help build my safety buffer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reminder:&lt;/strong&gt; do you know where all your pensions are, and how they are growing over time? Keep track of them.&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>tRPC, Astro and Cloudflare Workers</title><link>https://tlduck.com/blog/trpc-astro-cloudflare-workers/</link><guid isPermaLink="true">https://tlduck.com/blog/trpc-astro-cloudflare-workers/</guid><description>Putting tRPC and Astro on Cloudflare Workers.</description><pubDate>Sun, 19 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I haven&apos;t seen if &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;, &lt;a href=&quot;https://trpc.io/&quot;&gt;tRPC&lt;/a&gt; and &lt;a href=&quot;https://workers.cloudflare.com/&quot;&gt;Cloudflare Workers&lt;/a&gt; go well together. I found myself asking: must tRPC be in a separate package/deployment (e.g. &lt;code&gt;packages/&lt;/code&gt; in a monorepo, or separate Cloudflare Worker). I also managed to get Cloudflare bindings to work (e.g. D1, environment variables, R2). I&apos;m pleased to say it&apos;s relatively straight forward - there are a few options.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Update:&lt;/em&gt;&lt;/strong&gt; I last updated this page on 6th April 2023, as I use tRPC in my job, and Astro in my free time.&lt;/p&gt;
&lt;h2&gt;Background:&lt;/h2&gt;
&lt;p&gt;If you haven&apos;t heard of Cloudflare Workers, tRPC or Astro, read this. Otherwise, skip to &lt;a href=&quot;#options&quot;&gt;options&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare Workers:&lt;/strong&gt; cheap but fast serverless platform that integrates Cloudflare&apos;s other services: SQLite on the edge (D1), S3-alternative with zero egress fees (R2) and WebSockets (durable objects, also used by &lt;a href=&quot;https://news.ycombinator.com/item?id=34639728&quot;&gt;DriftDB&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tRPC&lt;/strong&gt; lets you &lt;em&gt;call your backend like you call functions&lt;/em&gt;. It&apos;s similar to gRPC, but without the code generation headache that comes with it. When using gRPC/protobufs, I&apos;ve found the codegen to be a very complex area:
&lt;ul&gt;
&lt;li&gt;There are multiple code generation tools for each language, with different features and bugs. They&apos;re not consistent. Not all features are implemented for each language.&lt;/li&gt;
&lt;li&gt;gRPC/protobuf code generation tools seem to be neglected.&lt;/li&gt;
&lt;li&gt;Code generation can be configured in different ways. This means the generated code is different and incompatible. Switching codegen tools to get their features (e.g. &lt;code&gt;google-protobuf/grpc-web&lt;/code&gt; to &lt;code&gt;protobuf-ts&lt;/code&gt;, or in Python: from &lt;code&gt;betterproto&lt;/code&gt; to &lt;code&gt;grpcio_tools&lt;/code&gt;) are major breaking changes.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Warning:&lt;/em&gt; Should tRPC even be used in Cloudflare Workers? It might have cold-start issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./workers-trpc.jpg&quot; alt=&quot;Discord chat between 2 users in Cloudflare Discord about tRPC on Cloudflare Workers&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I don&apos;t have these issues, and another discord user mentioned:
&lt;blockquote&gt;
&lt;p&gt;Cold starts definitely play a part but it just sounds like the codes own initialisation&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Astro&lt;/strong&gt; is a framework for developing web applications with any Javascript/Typescript framework. You can make static or server-side rendered (SSG) websites. You can write code that executes in the browser and in your backend, in the same file (when using SSG). It&apos;s like NextJS but it supports React, Svelte, Vue, Solid and more, in the same app (and UI component).&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;---
import Layout from &quot;../layouts/Layout.astro&quot;;
import { trpcClient } from &quot;../ts/trpcClient&quot;;

// Server-side: Call rest of backend APIs, e.g. createUser
const userId = await trpcClient.createUser.mutate({
	name: &quot;benbutterworth&quot;,
	bio: &quot;gardener&quot;,
});
const user = await trpcClient.getUserById.query(userId);
// Logged server-side (in cloudflare)
console.log({ user });
---

&amp;lt;Layout title=&quot;Welcome to Astro.&quot;&amp;gt;
	&amp;lt;main&amp;gt;
    &amp;lt;p&amp;gt;Generated server-side&amp;lt;/p&amp;gt;
		&amp;lt;h1&amp;gt;Welcome, {user.name}&amp;lt;/h1&amp;gt;
		&amp;lt;h2&amp;gt;User ID: {userId}&amp;lt;/h2&amp;gt;
		&amp;lt;h2&amp;gt;User bio: {user.bio}&amp;lt;/h2&amp;gt;
		&amp;lt;p&amp;gt;Generated client-side&amp;lt;/p&amp;gt;
		&amp;lt;h1 class=&quot;user2&quot;&amp;gt;&amp;lt;/h1&amp;gt;
		&amp;lt;h2 class=&quot;user2-id&quot;&amp;gt;&amp;lt;/h2&amp;gt;
		&amp;lt;h2 class=&quot;user2-bio&quot;&amp;gt;&amp;lt;/h2&amp;gt;
	&amp;lt;/main&amp;gt;
	&amp;lt;script&amp;gt;
		// Client-side: Call backend APIs, e.g. createUser
		import { trpcClient } from &quot;../ts/trpcClient&quot;;

		const userId = await trpcClient.createUser.mutate({
			name: &quot;Anonymous&quot;,
			bio: &quot;Interwebs&quot;,
		});
		const user = await trpcClient.getUserById.query(userId)
		// Logged client-side (browser console).
		console.log({ user });

		document.querySelector(&quot;h1.user2&quot;)!.textContent = `Hello, ${user.name}`;
		document.querySelector(&quot;h2.user2-id&quot;)!.textContent = userId;
		document.querySelector(&quot;h2.user2-bio&quot;)!.textContent = user.bio;
	&amp;lt;/script&amp;gt;
&amp;lt;/Layout&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zapp.run&lt;/strong&gt;: is a cool online Flutter IDE that makes use of Cloudflare Workers and Astro, and judging by &lt;a href=&quot;https://invertase.io/blog/astro-trpc-v10/&quot;&gt; Astro + tRPC v10&lt;/a&gt;, perhaps they use tRPC as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Options:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Simplest option: Avoid tRPC completely&lt;/li&gt;
&lt;li&gt;Simpler option: Cloudflare Pages&lt;/li&gt;
&lt;li&gt;Complex option: Cloudflare Pages + Cloudflare Worker&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Simplest option: Avoid tRPC completely&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;In practice, this isn&apos;t really an option.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Astro already supports server-side rendering. You can run code server-side above &lt;code&gt;---&lt;/code&gt; in your &lt;a href=&quot;https://docs.astro.build/en/core-concepts/astro-components/#component-structure&quot;&gt;components&lt;/a&gt;. You can already use your environment variables securely, and connect directly to private APIs/databases. You can already call end-to-end typesafe APIs, because each Astro page can be rendered server side.&lt;/p&gt;
&lt;p&gt;However, Astro doesn&apos;t give you a way to call your APIs &lt;em&gt;from the browser&lt;/em&gt; in a typesafe way, &lt;em&gt;after the page loads&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;Simpler option: Cloudflare Pages&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Both the backend code (tRPC, database access, R2 access) lives in the same &quot;package&quot; as the frontend.&lt;/li&gt;
&lt;li&gt;Example
&lt;ul&gt;
&lt;li&gt;website: https://trpc-astro-cloudflare.pages.dev/&lt;/li&gt;
&lt;li&gt;repo: https://github.com/uxduck/trpc-astro-cloudflare&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Positives:
&lt;ul&gt;
&lt;li&gt;Less configuration and code&lt;/li&gt;
&lt;li&gt;Simpler deployment. Your server-side code in &lt;code&gt;.astro&lt;/code&gt; files run on the same service.&lt;/li&gt;
&lt;li&gt;Single command to deploy&lt;/li&gt;
&lt;li&gt;Hot-restart works well. Saving a file leads to the backend rebuilding, and the frontend refreshing. In the example, run &lt;code&gt;pnpm dev&lt;/code&gt; to start the dev server.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Negatives:
&lt;ul&gt;
&lt;li&gt;Larger bundle size. Your frontend and backend application (or part of it) is in the same cloudflare pages project.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Deployed using Cloudflare Pages: &lt;code&gt;wrangler pages publish&lt;/code&gt; (internally uses Cloudflare Workers)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separation of concerns:&lt;/strong&gt; You can call other Cloudflare Workers from your Cloudflare Pages Function using &lt;a href=&quot;https://developers.cloudflare.com/pages/platform/functions/bindings/#service-bindings&quot;&gt;&quot;Cloudflare Pages Functions Service Bindings&quot;&lt;/a&gt;. This does not incur network latency because tRPC running on Cloudflare Workers uses the fetch API, which does not go over the network.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Complex option: Cloudflare Pages + Backend&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;This is approach is probably good if you&apos;ve already got a backend. This backend can be Cloudflare Workers running tRPC, Hono, Express, any combination of these, or anything else (e.g. NodeJS). Consider tRPC&apos;s &lt;a href=&quot;https://trpc.io/docs/express&quot;&gt;Express.js adapter&lt;/a&gt; or &lt;a href=&quot;https://www.npmjs.com/package/@hono/trpc-server&quot;&gt;Hono adapter&lt;/a&gt; (&lt;a href=&quot;https://github.com/honojs/hono/issues/582&quot;&gt;discussion&lt;/a&gt;). This allows you to avoid Cloudflare Worker platform for server-side rendering.&lt;/li&gt;
&lt;li&gt;Positives:
&lt;ul&gt;
&lt;li&gt;Deploy frontend and backend separately. Choose different services for frontend and backend.&lt;/li&gt;
&lt;li&gt;Separation: the code will not get entangled, because they&apos;re in separate packages. Forces you to be more strict.&lt;/li&gt;
&lt;li&gt;Cloudflare Workers separate from frontend: Cloudflare Pages (which uses Workers internally)
&lt;ul&gt;
&lt;li&gt;This separation means they are separate workers:
&lt;ul&gt;
&lt;li&gt;more control over worker / features&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Negatives:
&lt;ul&gt;
&lt;li&gt;More complex maintenance. e.g. You need to setup monorepo tool to build backend code before frontend is built. When deploying a preview environment, you need to deploy the backend. The frontend needs to be pointed to the correct backend. Preview environments are not self-contained.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Deployed as 1 Cloudflare Pages deployment, and 1 Cloudflare worker: &lt;code&gt;wrangler publish&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separation of concerns:&lt;/strong&gt; You can call other Cloudflare Workers using &lt;strong&gt;Service Bindings&lt;/strong&gt;. This also does not incur network latency because tRPC running on Cloudflare Workers uses the fetch API, which does not go over the network.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Summary 📒&lt;/h2&gt;
&lt;p&gt;I&apos;m currently trying &lt;strong&gt;tRPC with Astro on Cloudflare Pages&lt;/strong&gt;, with a single Cloudflare Pages Function. That is to say, I only have 1 worker: a &lt;strong&gt;fat worker&lt;/strong&gt;. Let me know if you have any questions.&lt;/p&gt;
&lt;h2&gt;Other resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/toyamarinyon/cloudflare-pages-plugin-trpc&quot;&gt;tRPC Pages Plugin&lt;/a&gt;: for both options, you can use this plugin for some convenience (to get Cloudflare bindings to D1, R2, etc.). I don&apos;t use it because it doesn&apos;t seem to support Astro.&lt;/li&gt;
&lt;li&gt;Warning: When calling another worker (service bindings), you need to use the Cloudflare API, which means you lose end-to-end typing / benefit of tRPC. tRPC has &lt;a href=&quot;https://trpc.io/docs/server/server-side-calls&quot;&gt;server-side calls&lt;/a&gt;, however that will not call another worker: it does not allow you to call workers with different implementations. This means you&apos;d only have 1 worker, and it would get large. You lose &quot;separation of concerns&quot; between multiple workers.
&lt;ul&gt;
&lt;li&gt;I initially thought: &quot;Surely there&apos;s a way to get the benefits of both tRPC and Cloudflare Service bindings&quot;.&lt;/li&gt;
&lt;li&gt;Edit: oh, see &lt;a href=&quot;https://arjunyel.com/blog/trpc-service-bindings/&quot;&gt;tRPC with no latency or network errors&lt;/a&gt;. Thanks &lt;code&gt;arjunyel.com&lt;/code&gt; - they did this to turn on Node compatibility just for the backend. However, IMHO if you don&apos;t need node compatibility, it doesn&apos;t make sense to do this unless you need to split your code across multiple workers - your worker bundle size might be too big (&amp;gt;1MB on free tier, &amp;gt;5MB on paid tier, or the size is affecting latency/start-up time). Or perhaps you have multiple teams and they want to deploy independently. It might get messy having multiple tRPC backends running and connecting to each other. I think tRPC being a backend framework where you can define all APIs &lt;em&gt;has tension with&lt;/em&gt; splitting your backend into multiple workers/bundles. It&apos;s possible, but I&apos;d recommend not doing it until performance or functionality is limited because of it. Also, I got an &lt;a href=&quot;https://discord.com/channels/595317990191398933/1059383290043310180/1092209788898771016&quot;&gt;answer from the blog author on Discord&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;createTRPCProxyClient&amp;lt;AppRouter&amp;gt;({
  links: [
    httpBatchLink({
      // Any URL will work since we are communicating with the worker directly
      url: &quot;https://www.example.com/trpc&quot;,
      // Pass in the Service binding after binding it (lol)
      fetch: env.BACKEND.fetch.bind(env.BACKEND),
    }),
  ],
});
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Warning: Even though Jetbrain&apos;s Webstorm IDE has an Astro plugin, it looks like it doesn&apos;t support tRPC. For example, you cannot navigate to the implementation of a query/mutation (&lt;code&gt;getUserById: t.procedure.input(z.string())...&lt;/code&gt;) from the astro file (&lt;code&gt;const user = await client.getUserById.query(userId);&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Example tRPC Backend&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;pages/trpc/api/[trpc].ts&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import { initTRPC } from &quot;@trpc/server&quot;;
import { fetchRequestHandler } from &quot;@trpc/server/adapters/fetch&quot;;
import { Context, createContext } from &quot;../context&quot;;
import { z } from &quot;zod&quot;;
import type { APIRoute } from &quot;astro&quot;;
import { trpcApiPath } from &quot;../trpcPath&quot;;
type User = {
  id: string;
  name: string;
  bio?: string;
};

// This has to match quite strongly to the schema. Considering using an ORM?
type UserDb = {
  UserID: string;
  name: string;
  bio?: string;
};

// Inpsired by https://invertase.io/blog/astro-trpc-v10/ and https://trpc.io/docs/fetch
export const t = initTRPC.context&amp;lt;Context&amp;gt;().create();

const appRouter = t.router({
  getUsers: t.procedure.query(async ({ ctx }) =&amp;gt; {
    try {
      // When running locally, it seems to use the wrong database. My local database has a Users table
      // See `wrangler d1 execute trpc-astro-cloudflare-template --command=&quot;SELECT name FROM sqlite_master WHERE type=&apos;table&apos;&quot; --local`.
      // However, error is
      // {
      //     message: &apos;D1_ERROR&apos;,
      //     cause: &apos;Error: SqliteError: no such table: Users&apos;
      //   }
      const stmt = ctx.db.prepare(&quot;SELECT * FROM Users LIMIT 10&quot;);
      const { results } = await stmt.all&amp;lt;UserDb&amp;gt;();

      return results?.map((user) =&amp;gt; ({
        id: user.UserID,
        name: user.name,
        bio: user.bio,
      }));
    } catch (e: any) {
      console.log({
        message: e.message,
        cause: e.cause.message,
      });
    }
  }),
  getUserById: t.procedure.input(z.string()).query(async ({ input, ctx }) =&amp;gt; {
    const result = await ctx.db
      .prepare(&quot;SELECT * FROM Users where UserID = ?1&quot;)
      .bind(input)
      .first();
    return { id: result.UserID, name: result.name, bio: result.bio };
  }),
  deleteUsers: t.procedure.mutation(async ({ ctx }) =&amp;gt; {
    await ctx.db.prepare(&quot;DELETE FROM Users&quot;).run();
  }),
  createUser: t.procedure
    // validate input with Zod
    .input(
      z.object({
        name: z.string().min(3),
        bio: z.string().max(142).optional(),
      }),
    )
    .mutation(async ({ input, ctx }) =&amp;gt; {
      const id = Date.now().toString();
      const user: User = { id, ...input };
      await ctx.db
        .prepare(&quot;INSERT INTO Users (UserID, name, bio) VALUES (?1, ?2, ?3);&quot;)
        .bind(id, user.name, user.bio)
        .run();
      return id;
    }),
});

// The Astro API route, handling all incoming HTTP requests.
export const all: APIRoute = ({ request }) =&amp;gt; {
  return fetchRequestHandler({
    req: request,
    endpoint: trpcApiPath,
    router: appRouter,
    createContext,
  });
};

export type AppRouter = typeof appRouter;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;src/ts/context.ts&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import type { inferAsyncReturnType } from &quot;@trpc/server&quot;;
import type { FetchCreateContextFnOptions } from &quot;@trpc/server/adapters/fetch&quot;;
import type { Env } from &quot;./worker-configuration&quot;;
import { getRuntime } from &quot;@astrojs/cloudflare/runtime&quot;;

export function createContext({
  req,
  resHeaders,
}: FetchCreateContextFnOptions) {
  // Get cloudflare bindings, as per https://docs.astro.build/en/guides/integrations-guide/cloudflare/#access-to-the-cloudflare-runtime
  const runtime = getRuntime&amp;lt;Env&amp;gt;(req);
  // console.log({runtime});
  // Now use a binding, for example: `runtime.env.SERVICE.fetch()`
  // Alternatively, get an environment variable with: `import.meta.env.SERVER_URL`
  // You can read custom or pre-defined environmment variables with e.g. import.meta.env.MODE, .BASE_URL, .CUSTOM_VAR, etc.

  const user = { name: req.headers.get(&quot;username&quot;) ?? &quot;anonymous&quot; };
  const db = runtime.env.DB;
  return { req, resHeaders, user, db };
}
export type Context = inferAsyncReturnType&amp;lt;typeof createContext&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Cloudflare Worker&apos;s Bindings in tRPC 🧙‍♀️</title><link>https://tlduck.com/blog/trpc-cloudflare-bindings/</link><guid isPermaLink="true">https://tlduck.com/blog/trpc-cloudflare-bindings/</guid><description>Getting cloudflare bindings (D1, R2, DO, KV, etc.) in tRPC.</description><pubDate>Sun, 14 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When using Cloudflare Workers, the main selling point is you can integrate with a lot of other Cloudflare APIs, like &lt;a href=&quot;https://developers.cloudflare.com/workers/platform/storage-options/#d1&quot;&gt;D1&lt;/a&gt; (database), &lt;a href=&quot;https://developers.cloudflare.com/workers/platform/storage-options/#r2&quot;&gt;R2&lt;/a&gt; (storage) and &lt;a href=&quot;https://developers.cloudflare.com/workers/learning/using-durable-objects&quot;&gt;Durable Objects&lt;/a&gt; (websockets), and more. However, there isn&apos;t any documentation on how to do that.&lt;/p&gt;
&lt;p&gt;The up-to-date way to implement Cloudflare Workers is by using the module workers syntax, as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default {
  async fetch(
    request: Request,
    env: Env, // 👈 not shown on the tRPC docs
    ctx: ExecutionContext, // 👈 not shown on the tRPC docs
  ): Promise&amp;lt;Response&amp;gt; {
    return fetchRequestHandler({
      endpoint: trpcApiPath,
      req: request,
      router: appRouter,
      createContext,
    });
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You get the &lt;code&gt;env&lt;/code&gt; object here, allowing you to access cloudflare bindings. Often, examples won&apos;t include the 2nd and 3rd parameters of &lt;code&gt;fetch&lt;/code&gt;, showing only &lt;code&gt;async fetch(request: Request): Promise&amp;lt;Response&amp;gt; {...}&lt;/code&gt;. For example, the &lt;a href=&quot;https://trpc.io/docs/server/adapters/fetch#create-cloudflare-worker&quot;&gt;tRPC docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Actually, we just need to use &lt;code&gt;env&lt;/code&gt; in &lt;code&gt;createContext&lt;/code&gt;. Pretty simple. So let&apos;s try.&lt;/p&gt;
&lt;h2&gt;Use &lt;code&gt;env&lt;/code&gt; in &lt;code&gt;createContext&lt;/code&gt; 🐛&lt;/h2&gt;
&lt;p&gt;The easiest option is to &quot;inline&quot; the &lt;code&gt;createContext&lt;/code&gt; argument. However, this &lt;strong&gt;won&apos;t&lt;/strong&gt; actually work because we need the &lt;code&gt;createContext&lt;/code&gt; function to instantiate tRPC / appRouter: &lt;code&gt;const t = initTRPC.context&amp;lt;Context&amp;gt;().create();&lt;/code&gt;, in another file. You might also get into a circular dependency hell.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { FetchCreateContextFnOptions, fetchRequestHandler } from &apos;@trpc/server/adapters/fetch&apos;;
import { appRouter } from &apos;./ts/trpc&apos;;
import { trpcApiPath } from &apos;./ts/trpcPath&apos;;
import { Env } from &apos;./ts/worker-configuration&apos;;
import { drizzle } from &apos;drizzle-orm/d1&apos;;

export default {
    async fetch(
        request: Request,
        env: Env,
        ctx: ExecutionContext
    ): Promise&amp;lt;Response&amp;gt; {
        return fetchRequestHandler({
            endpoint: trpcApiPath,
            req: request,
            router: appRouter,
            createContext: ({ req, resHeaders }: FetchCreateContextFnOptions) =&amp;gt;
          			// 🎸 You have access to `env` here, so
                // use a binding, for example: `env.SERVICE.fetch()`
                const user = { name: req.headers.get(&apos;username&apos;) ?? &apos;anonymous&apos; };
                const db = drizzle(env.DB);
                return { req, resHeaders, user, db };
            },
        });
    },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Refactoring... it works. 😍&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// src/index.ts
import {
  FetchCreateContextFnOptions,
  fetchRequestHandler,
} from &quot;@trpc/server/adapters/fetch&quot;;
import { appRouter } from &quot;./trpc/appRouter&quot;;
import { trpcApiPath } from &quot;./trpc/trpcPath&quot;;
import { Env } from &quot;./worker-configuration&quot;;
import { createContext } from &quot;./trpc/context&quot;;

export default {
  async fetch(
    request: Request,
    env: Env,
    ctx: ExecutionContext,
  ): Promise&amp;lt;Response&amp;gt; {
    if (request.method === &quot;OPTIONS&quot;) {
      const response = new Response(null, {
        status: 200,
        headers: {
          &quot;Access-Control-Allow-Origin&quot;: &quot;*&quot;,
          &quot;Access-Control-Allow-Headers&quot;: &quot;*&quot;,
        },
      });
      return response;
    }

    return fetchRequestHandler({
      endpoint: trpcApiPath,
      req: request,
      router: appRouter,
      createContext: (options: FetchCreateContextFnOptions) =&amp;gt;
        createContext({ ...options, env, ctx }),
    });
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// src/trpc/context.ts
import { FetchCreateContextFnOptions } from &quot;@trpc/server/adapters/fetch&quot;;
import { Env } from &quot;./ts/worker-configuration&quot;;
import { drizzle } from &quot;drizzle-orm/d1&quot;;

const createContext = async ({
  req,
  env,
  resHeaders,
}: FetchCreateContextFnOptions &amp;amp; { env: Env; ctx: ExecutionContext }) =&amp;gt; {
  console.log(`Gotttt itt: ${env.MY_ENV_VAR}`);
  // Now use a binding, for example: `env.SERVICE.fetch()`
  const user = { name: req.headers.get(&quot;username&quot;) ?? &quot;anonymous&quot; };
  const db = drizzle(env.DB);
  return { req, resHeaders, user, db };
};

export type Context = inferAsyncReturnType&amp;lt;typeof createContext&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Benefits 📈&lt;/h2&gt;
&lt;p&gt;This approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;avoids a third party dependency&lt;/li&gt;
&lt;li&gt;explains how to do it, so you can change it or extend it&lt;/li&gt;
&lt;li&gt;so when the Cloudflare Worker&apos;s API changes in the future, your understanding can help you upgrade to the new API.&lt;/li&gt;
&lt;li&gt;Avoids hijacking the Cloudflare &quot;entry point&quot;, so you can still use the official way of defining a Cloudflare worker. For example, to use static assets,&lt;/li&gt;
&lt;li&gt;There is an alternative (a plugin, &lt;code&gt;cloudflare-pages-plugin-trpc&lt;/code&gt;) which doesn&apos;t always work, for example when you&apos;re using Astro + tRPC + Cloudflare Workers. See it&apos;s &lt;a href=&quot;https://github.com/toyamarinyon/cloudflare-pages-plugin-trpc/blob/main/packages/pages-plugin-trpc/functions/%5B%5Bpath%5D%5D.ts&quot;&gt;internals&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I&apos;d be interested to know what you&apos;re building, and how you&apos;re using tRPC and Cloudflare!&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Running SeamlessM4T locally for translating text and speech</title><link>https://tlduck.com/blog/running-seamlessm4t-ai-locally/</link><guid isPermaLink="true">https://tlduck.com/blog/running-seamlessm4t-ai-locally/</guid><description>There are some minor tricks that I had to get right</description><pubDate>Fri, 05 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I found a YouTube video spoken in french, and I wanted to translate it to English. This was an opportunity to try SeamlessM4T. However, when I tried to use it, it ended up taking more than an hour. After finding the path of least resistance that actually worked, I wanted to share this with others.&lt;/p&gt;
&lt;p&gt;These steps are for the terminal. I used an ARM mac (M1 Pro). Intel macs are a bit more complicated because some dependencies need to be manually built locally (&lt;code&gt;fairseq2&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;SeamlessM4T is a family of models that can translate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;text to speech&lt;/li&gt;
&lt;li&gt;speech to text&lt;/li&gt;
&lt;li&gt;text to text&lt;/li&gt;
&lt;li&gt;speech to speech (my use case today, translating French videos into English)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Set up SeamlessM4T locally&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use Python 3.11, because 3.12 is not supported by torch or fairseq2
&lt;ul&gt;
&lt;li&gt;I already use pyenv, so I&apos;ll run: &lt;code&gt;pyenv local 3.11&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;You can install it too, but a simpler option is to uninstall python and download and install version 3.11 from https://www.python.org/downloads/&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Clone the &lt;a href=&quot;https://github.com/facebookresearch/seamless_communication&quot;&gt;repo&lt;/a&gt;: &lt;code&gt;git clone git@github.com:facebookresearch/seamless_communication.git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Change directory: &lt;code&gt;cd seamless_communication&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;python -m venv venv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;source venv/bin/activate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pip install torch fairseq2 -r dev_requirements.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pip install -e .&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;this downloads dependencies from pyproject.toml and makes the current directory an editable package&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;brew install libsndfile&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;When running &lt;code&gt;m4t_predict&lt;/code&gt; later, if you get an error &lt;code&gt;OSError: libsndfile is not found! Use your system package manager to install it &lt;/code&gt;, reinstall it by running &lt;code&gt;brew reinstall libsndfile&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;my extra step (for YouTube)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Download the YouTube video, using &lt;a href=&quot;https://github.com/yt-dlp/yt-dlp&quot;&gt;yt-dlp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Convert the video into wav, using &lt;a href=&quot;https://ffmpeg.org/&quot;&gt;ffmpeg&lt;/a&gt;, I ran &lt;code&gt;ffmpeg -i path/to/video.webm path/to/output-video.wav&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run it through seamless (in my case I&apos;m running the speech to speech task - s2st):&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;m4t_predict --task s2st --model_name seamlessM4T_medium --tgt_lang eng &apos;path/to/input.wav&apos; --output_path output`date +%Y-%m-%d.%H:%M:%S`.wav
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;strong&gt;Lag/crash warning&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;When running the default model (SeamlessM4T-Large v2) on my MacBook Pro with 16GB memory and M1 pro chip, it started lagging for a few minutes, &quot;your system has run out of application memory&quot; constantly popped up, and then my computer froze. &lt;em&gt;Now, I use the SeamlessM4T-Medium model, and it works.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./memory.png&quot; alt=&quot;macOS &amp;quot;force quit applications&amp;quot; window&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;ValueError: The input sequence length must be less than or equal to the maximum sequence length (4096), but is 11800 instead.&lt;/h2&gt;
&lt;p&gt;If this happens, your input audio is too long. You can manually split the a few clips, depending on how many words are spoken. I&apos;ll be writing a python script to split the audio clip into chunks of 4096 max, and then run seamless on each chunk, and merge the audio back together.&lt;/p&gt;
&lt;p&gt;A quick test is to just trim the input audio to 60 seconds chunks with ffmpeg, run &lt;code&gt;ffmpeg - i input.wav -t 60 output-60s-1.wav&lt;/code&gt; for the first clip, then start at 60s and end at 60s &lt;code&gt;ffmpeg - i input.wav -ss 60 -t 60 output-60s-2.wav&lt;/code&gt;, and so on.&lt;/p&gt;
&lt;h2&gt;To delete the downloaded models&lt;/h2&gt;
&lt;p&gt;The v2 large model (&lt;code&gt;seamlessM4T_v2_large.pt&lt;/code&gt;) is 8.4GB. You might not have enough space to keep all models locally.&lt;/p&gt;
&lt;p&gt;On macOS, models are stored in your home folder: /$HOME/.cache/fairseq2/assets, for example, &lt;code&gt;/Users/yourusername/.cache/fairseq2/assets&lt;/code&gt;. Open it in finder and delete files you don&apos;t want: &lt;code&gt;open $HOME/.cache/fairseq2/assets&lt;/code&gt; and delete them.&lt;/p&gt;
&lt;h2&gt;More resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;docs: https://github.com/facebookresearch/seamless_communication/tree/main/docs/m4t&lt;/li&gt;
&lt;li&gt;Seamless Tutorial: https://github.com/facebookresearch/seamless_communication/blob/main/Seamless_Tutorial.ipynb&lt;/li&gt;
&lt;li&gt;Languages supported: https://github.com/facebookresearch/seamless_communication/blob/main/docs/m4t/README.md#supported-languages&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Serverless functions and edge databases</title><link>https://tlduck.com/blog/serverless-functions-and-edge-databases/</link><guid isPermaLink="true">https://tlduck.com/blog/serverless-functions-and-edge-databases/</guid><description>What I learnt using serverless and edge databases.</description><pubDate>Sun, 21 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here are things I learnt when building applications using serverless functions (primarily Cloudflare Workers, previously GCP Functions) and edge/distributed databases (Turso, Cloudflare D1, Neon).&lt;/p&gt;
&lt;p&gt;This article isn&apos;t polished because I&apos;m focused on building, not writing. Also, in the spirit of start ups, ship things and get feedback! I&apos;ll keep this article updated as I build more stateful personal projects.&lt;/p&gt;
&lt;h2&gt;Why did I even bother with serverless? 🧑‍🎓&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Low cost, and often generous free tier&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare even provides &lt;strong&gt;serverless WebSockets&lt;/strong&gt; (using Websockets over Workers and Durable Objects). Unfortunately, this is &lt;strong&gt;expensive&lt;/strong&gt;, up to $5 a month per shared state - for example each chat room can cost you up to $5 a month).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Low latency / high performance:&lt;/strong&gt; if the runtime and database is managed, the service providers (e.g. Cloudflare, Vercel, AWS, etc.) can run code and keep data close to your users.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DX Hype:&lt;/strong&gt; Lots of hype around new products, all screaming &quot;great developer experience&quot;. Everything is managed...&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Curiosity:&lt;/strong&gt; It&apos;s exciting to learn about unconventional, futuristic infrastructure made by companies like Fly, Neon, Turso and Cloudflare.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run locally:&lt;/strong&gt; I assumed all these services would be easy to run locally, like any conventional architecture. Unfortunately, this is not the case. Ironic, given that these services are meant to run everywhere/on-the-edge.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What went wrong 🐩💩&lt;/h2&gt;
&lt;p&gt;Almost everything I tried seemed to have rough edges.&lt;/p&gt;
&lt;h3&gt;Why not &lt;a href=&quot;https://developers.cloudflare.com/d1&quot;&gt;Cloudflare D1&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Not open:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API access: I don&apos;t want to put my data into D1 because it is closed to Cloudflare. You cannot access it from Fly.io, GCP or others directly.&lt;/li&gt;
&lt;li&gt;Closed source&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Not distributed or replicated:&lt;/strong&gt; It&apos;s a single instance. During the alpha, the database was a single instance of SQLite in a specific datacenter in the US. Now, you can choose where this instance lives. However, there is no replication or sharding. Even in the future, I expect writes will all go to a single instance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Doesn&apos;t run locally:&lt;/strong&gt; For a while, this was not possible. There is no documentation about it and it is not listed in the &lt;a href=&quot;https://github.com/cloudflare/miniflare#features&quot;&gt;Miniflare features&lt;/a&gt;. However, the older miniflare 2&apos;s help pages mentioned &lt;code&gt;--d1&lt;/code&gt; and &lt;code&gt;--d1-persist&lt;/code&gt;. Unfortunately, &lt;code&gt;miniflare&lt;/code&gt; CLI is deprecated, and it&apos;s only for &lt;code&gt;wrangler&lt;/code&gt; to use now. Now it&apos;s not clear. After configuring miniflare and running a quick check, it looks like the &lt;code&gt;D1Database&lt;/code&gt; binding tries to connect to the cloud D1 instance, and &lt;strong&gt;freezes forever&lt;/strong&gt; - it doesn&apos;t time out.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I turned the wifi on my laptop 5 minutes later and it connected and made the query. 😅&lt;/li&gt;
&lt;li&gt;If someone has a way of running D1 locally with the latest wrangler and miniflare (v3), let me know!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lagging DX:&lt;/strong&gt; In general, I find Cloudflare are really fast at releases new product lines (e.g. D1, R2, Queues, Constellation) but the developer experience is quite bad.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bad, unintuitive error handling:&lt;/strong&gt; To read the error message, you need to log it in a &lt;a href=&quot;https://developers.cloudflare.com/d1/platform/client-api/#errors&quot;&gt;specific, documented way&lt;/a&gt;, otherwise you&apos;ll only see an unhelpful &lt;code&gt;D1_ERROR&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;try {
  await db.exec(&quot;INSERTZ INTO my_table (name, employees) VALUES ()&quot;);
} catch (e: any) {
  console.log({
    message: e.message,
    cause: e.cause.message,
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Why not &lt;a href=&quot;https://turso.tech/&quot;&gt;Turso&lt;/a&gt; 🤖&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Can&apos;t run Turso &lt;strong&gt;locally&lt;/strong&gt; with Cloudflare Workers.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;turso dev&lt;/code&gt; is not maintained, so I needed to install &lt;code&gt;sqld&lt;/code&gt; and use that instead. That also didn&apos;t work.&lt;/li&gt;
&lt;li&gt;&quot;For now the best will be to just create a new database for development in the actual service.&quot; - CEO of ChiselStrike, who develop Turso.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fork&lt;/strong&gt; of SQLite means I hit some issues that wouldn&apos;t have been the case for SQLite, since there are protocols used for communication.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Can&apos;t &lt;strong&gt;export/dump&lt;/strong&gt; data: https://github.com/chiselstrike/turso-cli/issues/433&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Thought:&lt;/em&gt; I&apos;ll be willing to try Turso again once I can run it locally and behaves like a normal database.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why not serverless functions, in general? 📈&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;APIs are &lt;strong&gt;platform specific&lt;/strong&gt;. Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The serverless functions you wrote for AWS Lambda cannot just be run on Cloudflare Workers, since Workers doesn&apos;t use NodeJS.&lt;/li&gt;
&lt;li&gt;This is extremely annoying when you find a bug and you want to switch to another service provider, but your code or your dependencies do not support another provider.&lt;/li&gt;
&lt;li&gt;This also means you have to learn how the service works.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Restrictions and limitations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;for a long time, Cloudflare Workers had CPU limits of 50ms, even for paid users. This meant you couldn&apos;t even hash passwords with bcrypt. Workers still have a low memory limit, so I still struggled with image resizing in Cloudflare Workers recently.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;NextJS functions don&apos;t support WebSockets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Some have bad developer experience. For a long time, the only way to debug Cloudflare Workers was &lt;code&gt;console.log&lt;/code&gt;, and this is described below. FWIW: this has improved with Miniflare, though I have had a lot of issues getting that to work.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;blockquote&gt;
&lt;p&gt;Inserting &lt;strong&gt;console.log lines throughout your code&lt;/strong&gt; can help you understand the state of your application in various stages until you reach the desired output. - &lt;a href=&quot;https://stackoverflow.com/questions/71378209/how-to-put-breakpoints-and-debug-cloudflare-workers&quot;&gt;How to put breakpoints and debug Cloudflare Workers?&lt;/a&gt;, originally &lt;a href=&quot;https://developers.cloudflare.com/workers/learning/debugging-workers&quot;&gt;Debugging Workers&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cold start time:&lt;/strong&gt; to be efficient, serverless services don&apos;t keep your application running all the time. This means it pays for the cost for launching your application on every request. It is only launched when a user makes a request, and might stay alive for some time, like a cache.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Example article: &lt;a href=&quot;https://cal.com/blog/cal-com-cold-start-resolution-blog&quot;&gt;Cal.com Cold Start Resolution&lt;/a&gt; - &lt;a href=&quot;https://news.ycombinator.com/item?id=35963820&quot;&gt;HN comments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cloudflare tries to avoid this by using a more efficient runtime that starts up quicker.&lt;/li&gt;
&lt;li&gt;In a conventional, non serverless system, you pay the startup cost once, and it is invisible to users, since requests will be routed to the older instance until the new one is ready. You might also have multiple instances.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Expensive: Often serverless functions are good for low and unpredictable loads. If it&apos;s predictable, you can autoscale to handle the load, and if the load is high, it&apos;s cheaper to have dedicated machines.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bad design:&lt;/strong&gt; passing data between serverless functions means copying data, and sometimes even running the same code multiple times (e.g. authorization checks). This takes time, and time is money for serverless functions (and users). If you had your logic in one application, your total execution time, CPU utilisation and memory utilisation is lower. Your bill will likely be higher if these are higher. AWS have shown they &lt;a href=&quot;https://www.primevideotech.com/video-streaming/scaling-up-the-prime-video-audio-video-monitoring-service-and-reducing-costs-by-90&quot;&gt;reduced cost by 10x moving distributed serverless functions to a monolith one&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Overall, you trade old, solved problems for new problems that can be platform specific.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Neon Postgres 🌟&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Currently experimenting with Postgres, Neon.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Vercel Postgres&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://vercel.com/docs/storage/vercel-postgres&quot;&gt;Doesn&apos;t support drizzle yet.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Probably uses Neon under the hood.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My chosen tech stack 🤔&lt;/h2&gt;
&lt;p&gt;Still in progress... 🔨&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Currently experimenting with Postgres, Neon.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If I want to connect multiple users with state and websockets, then use Fly.io/containers and use websockets as normal, as opposed to serverless and platform specific APIs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion 👋&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Offline last:&lt;/strong&gt; Many serverless products have relatively bad developer experience even when the marketing sounds like you can &quot;get X done in 5 minutes&quot;. Running things locally is pretty important for me, because it means I can work completely offline when travelling, and tests run very quickly.
&lt;ul&gt;
&lt;li&gt;I wanted to run it locally since I&apos;ve been taking flights and trains journeys with no internet, and sometimes am located in areas with really bad internet.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous improvement:&lt;/strong&gt; All the products I mentioned before I continuously improving, I look forward to building something using boring technology. See you in 2025, serverless SQLite databases.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1 Question 👋&lt;/h2&gt;
&lt;p&gt;Which serverless services would you recommend?&lt;/p&gt;
&lt;h2&gt;Bonus: Serverless WebSockets 💸&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;on Cloudflare:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;It is &lt;a href=&quot;https://community.cloudflare.com/t/is-my-understanding-of-the-websockets-pricing-model-correct/384366/2&quot;&gt;expensive&lt;/a&gt;. See &lt;a href=&quot;https://discord.com/channels/595317990191398933/773219443911819284/1106601223194423326&quot;&gt;discord message&lt;/a&gt;: &quot;Its &lt;strong&gt;$4.22/mo&lt;/strong&gt; for each DO (if active all month) not $5 per hour. But the team has been working on a hibernation API that will sleep the durable object if it isnt actively handling a message from a websocket. No clue when that will actually come out but its been in talks the last few months.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It hasn&apos;t gotten cheaper over time. &quot;They&apos;re working on improving pricing&quot; has been suggested for more than a year.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;See &lt;a href=&quot;https://discord.com/channels/595317990191398933/1108808908732121188/1108818978979184690&quot;&gt;discord message&lt;/a&gt;: &quot;in the past they have talked about making websockets not count as active time in order to make websocket pricing better which would mean it can sleep. But I assume &lt;strong&gt;if/when&lt;/strong&gt; that happens it will be a new API specific to allowing the object to sleep with websocket connections but we will find out whenever that happens if it does&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It&apos;s not supported by popular backend frameworks in Node. e.g. &lt;a href=&quot;https://trpc.io/&quot;&gt;tRPC&lt;/a&gt;, Fastify, express, &lt;a href=&quot;https://hono.dev&quot;&gt;hono&lt;/a&gt;, or &lt;a href=&quot;https://github.com/kwhitley/itty-router&quot;&gt;itty-router&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It&apos;s a pretty confusing API. To actually get multiple clients connected together, there is only 1 paragraph of &lt;a href=&quot;https://developers.cloudflare.com/workers/learning/using-websockets/#durable-objects-and-websocket-state&quot;&gt;documentation on Cloudflare&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;blockquote&gt;
&lt;p&gt;If your application needs to coordinate among multiple WebSocket connections, such as a chat room or game match, you will need to create a Durable Object so clients send messages to a single-point-of-coordination. Durable Objects are a coordinated state tool for Cloudflare Workers, which are often used in parallel with WebSockets to persist state over multiple clients and connections. Refer to &lt;a href=&quot;https://developers.cloudflare.com/workers/learning/using-durable-objects/&quot;&gt;Durable Objects&lt;/a&gt; to get started.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To optimise the cost, you could consider sharing a single durable objects between different &quot;channels&quot;, but this adds to the mess. For example, the first approach is to create a durable object, and all clients connect to a worker which then connects to a durable object managing a single group chat. However, if you&apos;re a chat application, this doesn&apos;t scale with group chats, since each group chat would be ~$5/month. Instead, your durable object should handle multiple group chats. Oh, and keep in mind that if it does too much, it will &lt;a href=&quot;https://developers.cloudflare.com/workers/learning/using-durable-objects#error-durable-object-is-overloaded&quot;&gt;drop requests&lt;/a&gt;: . Doesn&apos;t sound like it scales nicely.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;on AWS Lambda&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&apos;s possible&lt;/li&gt;
&lt;li&gt;Lock in: AWS only API&lt;/li&gt;
&lt;li&gt;I have not used it, so I can&apos;t say too much. I&apos;m not interested in serious-lock-in products, especially the AWS ones.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Bonus: Why not Vitess&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Vitess isn&apos;t really an edge database, but it is a sharded MySQL database used by Stripe, YouTube, Slack, GitHub and more.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Expensive: The first database is free, but the second is $29 a month. That&apos;s a sudden increase, that is not tied to &lt;strong&gt;usage&lt;/strong&gt;. 🤔&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I also don&apos;t have the problems of YouTube scale.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Application configuration, a simple approach.</title><link>https://tlduck.com/blog/environment-variables/</link><guid isPermaLink="true">https://tlduck.com/blog/environment-variables/</guid><description>A simple approach to a problem that always comes up in projects.</description><pubDate>Tue, 21 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A quick post about setting up app configuration using environment variables (or files).&lt;/p&gt;
&lt;h2&gt;Why?&lt;/h2&gt;
&lt;p&gt;There are the the standard reasons of avoiding committing production secrets in code, like Rabbit R1. However, I&apos;ve come across more projects that &lt;strong&gt;do&lt;/strong&gt; rely on &lt;code&gt;.env&lt;/code&gt; files, but simply haven&apos;t documented what the &lt;code&gt;.env&lt;/code&gt; should look like, or where to get API keys, database connection strings, etc. Because it&apos;s gitignored, it&apos;s a puzzle to figure out what goes in it.&lt;/p&gt;
&lt;p&gt;I&apos;m writing this so I can signpost developers to a simple, maintainable way to set up configuration in their projects. I&apos;d love your feedback would gladly update it if you&apos;ve got better ideas.&lt;/p&gt;
&lt;h2&gt;Benefits of my approach&lt;/h2&gt;
&lt;p&gt;Keeps project configuration maintainable. You get a clear error immediately when the app starts, instead of getting confused by weird application or library behaviour because a string was empty later. You have up to date configuration documentation.&lt;/p&gt;
&lt;p&gt;Bonus: You can also add additional error messages and documentation when the configuration is wrong or out of date, to help migrate existing developers local &lt;code&gt;.env&lt;/code&gt; files when you make changes to it.&lt;/p&gt;
&lt;h2&gt;Environment variables&lt;/h2&gt;
&lt;p&gt;We can configure variables in &lt;code&gt;.env&lt;/code&gt; files, and use libraries to automatically read and load text from them. Production applications would have the environment variables setup through a secrets manager or a web UI. Local application development would rely on local, &lt;code&gt;.env&lt;/code&gt; files.&lt;/p&gt;
&lt;h3&gt;Git ignored &lt;code&gt;.env&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;You have 2 files: &lt;code&gt;.env&lt;/code&gt; (git ignored) and &lt;code&gt;example.env&lt;/code&gt; (not gitignored), which have identical structure. Every variable in the file contains a comment / documentation on how to set it up. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# REMINDER: Update the `example.env` file when you update `.env`
# Database
# These credentials already match the local development database in docker (docker-compose.yml)
DATABASE_URL=postgres://user:password@127.0.0.1:5432/appname
## Staging (Supabase) - SSL/TLS is required by configuring this in the Supabase project settings
#DATABASE_URL=
# Production (Another DB service) - add some useful links here
#DATABASE_URL=
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the README, one of the setup steps would be: &quot;Create a .env file from the template &lt;code&gt;cp example.env .env&lt;/code&gt; and update according to the comments&quot;. This keeps documentation of environment variables in the same place where it gets changed, and avoids duplication of documentation in README files (easily gets out of date).&lt;/p&gt;
&lt;h3&gt;Committed &lt;code&gt;.env&lt;/code&gt; + git ignored &lt;code&gt;.env.local&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This is a similar approach, but I don&apos;t like it because IDEs don&apos;t automatically syntax highlight this file, since it ends with &lt;code&gt;.local&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Validation&lt;/h2&gt;
&lt;p&gt;I validate environmnet variables, and I do that at application start-up time because I prefer the peace of mind that all the required API keys, backend URLs, database connection strings, etc. are already set correctly anywhere in my application.&lt;/p&gt;
&lt;p&gt;I&apos;ll share some simplified examples.&lt;/p&gt;
&lt;h3&gt;Typescript example&lt;/h3&gt;
&lt;p&gt;I use zod types. I can import &lt;code&gt;env&lt;/code&gt; anywhere else in the application.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { z } from &quot;zod&quot;;

const envSchema = z.object({
  LOG_DATABASE: z.coerce.boolean().default(false),
  OPENAI_API_KEY: z.string(),
  DATABASE_URL: z
    .string()
    .describe(
      [
        &quot;Connection string to postgres database, starting with `postgres://`.&quot;,
        &quot;Can be local or remote. Get the remote database from your project.&quot;,
      ].join(&quot; &quot;),
    ),
  PORT: z
    .string()
    .describe(
      [
        &quot;The port the backend application should listen on.&quot;,
        &quot;Useful when hosting on services that want to configure which&quot;,
        &quot;port is listened on, like Fly.io. Defaults to 4000.&quot;,
      ].join(&quot; &quot;),
    )
    .optional(),
});

// Load environment variables just before reading and validating them
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const dotenvPath = path.resolve(__dirname, &quot;../.env&quot;);
dotenv.config({ path: dotenvPath });
export const env = envSchema.parse(process.env);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Python example&lt;/h3&gt;
&lt;p&gt;I use pydantic.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pydantic import BaseModel
from dotenv import load_dotenv
import os

load_dotenv()

class Config(BaseModel):
    LOG_DATABASE: bool = False
    DATABASE_URL: str
    PORT: int

config = Config(**os.environ)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Bonus Python recommendation:&lt;/em&gt; I use uv by Astral, instead of Poetry, pdm, pyenv or anything else. It seems to have simplified python and python package management.&lt;/p&gt;
&lt;h3&gt;Configuration files&lt;/h3&gt;
&lt;p&gt;For more complicated projects and special cases, I use yaml config files. This isn&apos;t an option if you can&apos;t easily swap out the config files. For example, on Fly.io, it&apos;s really easy to change environment variables and restart the application, but replacing the config file requires a new deploy. However, if you&apos;re using Kubernetes/Helm, then yaml files fit in well.&lt;/p&gt;
&lt;p&gt;The validation above can be used directly on the data generated from config files, including easily adding nested objects. I prefer yaml because it&apos;s easier to read and edit, but you can use JSON or XML too.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;I usually go with the environment variables + validation in my side projects because I don&apos;t need complex structures available in yaml/json files.&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item><item><title>Should I Close Source my Company? Replying to Supabase</title><link>https://tlduck.com/blog/close-source/</link><guid isPermaLink="true">https://tlduck.com/blog/close-source/</guid><description>I was reading Supabase&apos;s &apos;Should I Open Source my Company?&apos;, and wanted to add some nuance which might be useful for certain companies and industries.</description><pubDate>Fri, 16 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;I&apos;ve been thinking about open source in the defence tech industry. I was reading Supabase&apos;s &lt;a href=&quot;https://supabase.com/blog/should-i-open-source-my-company&quot;&gt;Should I Open Source my Company?&lt;/a&gt;, which highlights the benefits of building an open source company like Supabase. I wanted to add some nuance which might be useful for certain companies and industries. All things being equal, I would strongly prefer working on open source or running an open source company. Unfortunately, not all things are equal. For example, in the context of Russian invasion of Ukraine, innovations in Ukraine last between 3 to 6 months and we need to constantly innovate just to keep up. l&apos;d hate to open source key technologies and reduce that window of advantage to adversaries who invade countries.&lt;/p&gt;
&lt;h2&gt;Benefits&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Developers get to use their code even after they leave the company.&lt;/li&gt;
&lt;li&gt;It &lt;a href=&quot;https://gwern.net/complement&quot;&gt;commoditises your complement&lt;/a&gt;. Some of the functionality that your competitors would have to reimplement are now commodities, where customers can more easily and cheaply get them. The value capture for a competitor to implement those features is now lower.&lt;/li&gt;
&lt;li&gt;Marketing to customers. If you were a consulting company and published some popular open source devtools, you&apos;ll be top of mind when the users of the devtools need extra help.&lt;/li&gt;
&lt;li&gt;You get to show your code to others, which helps with employment, branding, starting-a-startup and I think, developer street-cred (only between developers and only if the code is good 😂️).&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://supabase.com/blog/should-i-open-source-my-company&quot;&gt;supabase article&lt;/a&gt; has a lot more benefits, I&apos;m keeping it DRY&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Challenges&lt;/h2&gt;
&lt;p&gt;There are some circumstances where open source might be more difficult. Supabase avoids some challenges because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;PaaS technology is iterative, not ground breaking: Supabase is a PaaS where there is a lot of competition and improvements are iterative. There is not much code that is &quot;novel&quot; (intellectual property someone wants to steal), it&apos;s an iteration on existing software. Extremely novel systems (e.g. algorithms, e.g. path planning/scheduling algorithms) are unique/novel, and open sourcing them could mean a competitor improves their efficiency. Please tell me if I am wrong here.&lt;/li&gt;
&lt;li&gt;open source is a marketing strategy that works when selling to developers. If Supabase was the first PaaS in the world, they&apos;d probably get customers even if they weren&apos;t open source. Closed-source Firebase already exists, and plenty of other open source ones too. This works doubly well when developer-focused articles/memes are written about open source. Preaching to the choir.&lt;/li&gt;
&lt;li&gt;it operates in a meritocratic industry. Developers are knowledgeable and decide which platforms to use. Some industries are extremely sketchy, where customers have no idea which product is better. They may just choose the biggest brand name. If that brand name can tick a box (implements novel algorithm), many customers will choose that big brand name over the original creators of the tool.&lt;/li&gt;
&lt;li&gt;Their industry respects licensing. PaaS competitors have highly paid, respectful engineers who would not accept code theft or license violations. Other companies like Elastic, Terraform, Meta have licenses that treat large companies differently. This would likely be respected by competitors who can compete effectively, such as AWS and Azure. In defence, someone told me &quot;don&apos;t work with X or Y (billion dollar companies in defence) because they have a bad reputation of stealing ideas from startups or acquiring them to effectively shut them down.&quot; Supabase&apos;s license is very open, so this point is more for other companies.&lt;/li&gt;
&lt;li&gt;the (self-)hosting and scaling aspect of Supabase is &lt;a href=&quot;https://github.com/orgs/supabase/discussions/21558&quot;&gt;actually difficult&lt;/a&gt;, and can change at any time. This is not because of Supabase&apos;s &quot;evil&quot; intentions. Supabase does not open source &lt;em&gt;everything&lt;/em&gt;. Even then, documentation about DevOps might be on a different system, not GitHub. I&apos;ll let the users or maintainers of Supabase add more detail here if possible (they&apos;re an incredibly honest, knowledgeable and open team)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; think about what type of industry you&apos;re in, the customers you have and how that affects theft, license violation or simply legal copying. If open source, consider broadcasting your status by writing many articles about it. Also, don&apos;t prioritise self hosting documentation, just focus on your users - that is the priority (this is not malicious, it&apos;s business). Think about the value that a competitor will get by reading or copying your code, and hide some important bits. Then you can decide which parts of your company to open source without turning your for-profit into a charity or bankruptcy.&lt;/p&gt;
&lt;p&gt;This wasn&apos;t meant to be a dig at Supabase. I respect their product and the major bits they&apos;ve open sourced. I know people who have worked there. I just want the other side of the story to be written down, from the perspective of someone who&apos;s worked in closed source, open source and then closed source again. I still try to open source certain things.&lt;/p&gt;
&lt;p&gt;Disagree? Agree? Distasteful? Leave a comment!&lt;/p&gt;
</content:encoded><author>Ben Butterworth</author></item></channel></rss>