Congratulations!

[Valid RSS] This is a valid RSS feed.

Recommendations

This feed is valid, but interoperability with the widest range of feed readers could be improved by implementing the following recommendations.

Source: http://akrabat.com/feed/

  1. <?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
  2. xmlns:content="http://purl.org/rss/1.0/modules/content/"
  3. xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  4. xmlns:dc="http://purl.org/dc/elements/1.1/"
  5. xmlns:atom="http://www.w3.org/2005/Atom"
  6. xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
  7. xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
  8. >
  9.  
  10. <channel>
  11. <title>Rob Allen</title>
  12. <atom:link href="https://akrabat.com/feed/" rel="self" type="application/rss+xml" />
  13. <link>https://akrabat.com</link>
  14. <description>Pragmatism in the real world</description>
  15. <lastBuildDate>Tue, 08 Jul 2025 11:37:33 +0000</lastBuildDate>
  16. <language>en-US</language>
  17. <sy:updatePeriod>
  18. hourly </sy:updatePeriod>
  19. <sy:updateFrequency>
  20. 1 </sy:updateFrequency>
  21. <generator>https://wordpress.org/?v=6.8.2</generator>
  22. <item>
  23. <title>Notarising a macOS standalone binary</title>
  24. <link>https://akrabat.com/notarising-a-macos-standalone-binary/</link>
  25. <comments>https://akrabat.com/notarising-a-macos-standalone-binary/#respond</comments>
  26. <dc:creator><![CDATA[Rob]]></dc:creator>
  27. <pubDate>Tue, 15 Jul 2025 09:00:00 +0000</pubDate>
  28. <category><![CDATA[Command Line]]></category>
  29. <category><![CDATA[Development]]></category>
  30. <category><![CDATA[Shell Scripting]]></category>
  31. <guid isPermaLink="false">https://akrabat.com/?p=7413</guid>
  32.  
  33. <description><![CDATA[I've been writing a simple Swift command line tool called QuickSS. It's a single file swift file, that I compile to a standalone binaryusing: swiftc quickss.swift -o quickss To distribute it on modern Macs, I need to sign it and then get Apple to notarise it. Signing the binary To sign the binary, you need a "Developer ID Application" certificate from your paid developer account. If you don't have one there already create a new… <a href="https://akrabat.com/notarising-a-macos-standalone-binary/">continue reading</a>.]]></description>
  34. <content:encoded><![CDATA[<p>I've been writing a simple Swift command line tool called QuickSS. It's a single file swift file, that I compile to a standalone binaryusing:</p>
  35. <pre>
  36. swiftc quickss.swift -o quickss
  37. </pre>
  38. <p>To distribute it on modern Macs, I need to sign it and then get Apple to notarise it.</p>
  39. <h2>Signing the binary</h2>
  40. <p>To sign the binary, you need a "Developer ID Application" certificate from your paid <a href="https://developer.apple.com/account/resources/certificates/list">developer account</a>. If you don't have one there already create a new one, which requires a CSR from Keychain. Fortunately, the <a href="https://developer.apple.com/help/account/certificates/create-a-certificate-signing-request">info on what to do</a> is clear.</p>
  41. <p>Download your Developer ID Application certificate and add to Keychain Access.</p>
  42. <p>Next you need its name. This is done using <tt>security find-identity -v -p codesigning</tt> which gives you a list of certs. You want the text between the <tt>"</tt> that starts "Developer ID Application:". The easiest way to get this if you are scripting it is:</p>
  43. <pre>
  44. IDENTITY=$(security find-identity -v -p codesigning | grep "Developer ID Application" | grep -m 1 -oE '"[^"]+"' | tr -d '"')
  45. </pre>
  46. <p>To actually sign the binary, use the <tt>codesign</tt> utility:</p>
  47. <pre>
  48. codesign --timestamp --options runtime --sign "$IDENTITY" quickss
  49. </pre>
  50. <h2>Notarising</h2>
  51. <p>To notarise the binary, you use the <tt>notarytool</tt> utility within <tt>xcrun</tt>. This expects a zip or pkg file, so zip up the binary first. You also need to know your Apple developer account's Team ID, your Apple ID and you need an app-specific password from <a href="https://account.apple.com">account.apple.com</a>.</p>
  52. <p>Submit your app for notarising with:</p>
  53. <pre>
  54. zip -q quickss.zip quickss
  55.  
  56. xcrun notarytool submit  --wait --no-progress -f json \
  57.    --team-id "$TEAM_ID" \
  58.    --apple-id "$APPLE_ID" \
  59.    --password "$APP_SPECIFIC_PASSWORD" \
  60.    quickss.zip
  61.  
  62. rm quickss.zip
  63. </pre>
  64. <p>If it fails, use the submission ID to query the log:</p>
  65. <pre>
  66. xcrun notarytool log \
  67.    --team-id "$TEAM_ID" \
  68.    --apple-id "$APPLE_ID" \
  69.    --password "$APP_SPECIFIC_PASSWORD" \
  70.    "&lt;submission ID&gt;"
  71. </pre>
  72. <p>Apple has now notarised your app.</p>
  73. <h2>Note: No need to staple</h2>
  74. <p>For a standard Mac applications (<tt>.app</tt> bundles), you can "staple" the notarisation into it. This makes things more efficient for the GateKeeper technology. However, for a standalone binary, <em>this cannot be done</em> as there is no directory into which to store the notarisation file.</p>
  75. <p>If you try to staple, you'll get <tt>The staple and validate action failed! Error 73</tt> with no further information which is not entirely helpful.</p>
  76. <p>For standalone binaries GateKeeper checks directly with Apple's servers the first time they are run, so stapling is unnecessary.</p>
  77. <h2>That's it</h2>
  78. <p>That's it. You now have a notarised standalone binary that can be easily distributed.</p>
  79. ]]></content:encoded>
  80. <wfw:commentRss>https://akrabat.com/notarising-a-macos-standalone-binary/feed/</wfw:commentRss>
  81. <slash:comments>0</slash:comments>
  82. </item>
  83. <item>
  84. <title>Using the 1Password CLI in a script</title>
  85. <link>https://akrabat.com/using-the-1password-cli-in-a-script/</link>
  86. <comments>https://akrabat.com/using-the-1password-cli-in-a-script/#respond</comments>
  87. <dc:creator><![CDATA[Rob]]></dc:creator>
  88. <pubDate>Tue, 08 Jul 2025 09:00:00 +0000</pubDate>
  89. <category><![CDATA[Command Line]]></category>
  90. <category><![CDATA[Development]]></category>
  91. <category><![CDATA[Shell Scripting]]></category>
  92. <guid isPermaLink="false">https://akrabat.com/?p=7407</guid>
  93.  
  94. <description><![CDATA[I'm currently writing a script that notarises a macOS CLI app which needs to access a password. Rather than put it in an environment variable, I thought I'd use the 1Password CLI. This is the first time I've used it, so these are my notes. The 1Password CLI tool is call op. I installed it via Homebrew with: brew install 1password-cli Sign in You need to sign in. op signin As I have multiple accounts… <a href="https://akrabat.com/using-the-1password-cli-in-a-script/">continue reading</a>.]]></description>
  95. <content:encoded><![CDATA[<p>I'm currently writing a script that notarises a macOS CLI app which needs to access a password. Rather than put it in an environment variable, I thought I'd use the 1Password CLI. This is the first time I've used it, so these are my notes.</p>
  96. <p>The 1Password CLI tool is call <tt>op</tt>. I installed it via Homebrew with:</p>
  97. <pre>brew install 1password-cli</pre>
  98. <h2>Sign in</h2>
  99. <p>You need to sign in. </p>
  100. <pre>op signin</pre>
  101. <p>As I have multiple accounts as various clients have shared access to specific vaults, it asks me which account I want to sign in. To save this step, you can set the <tt>OP_ACCOUNT</tt> environment variable:</p>
  102. <pre>export OP_ACCOUNT=my.1password.com</pre>
  103. <p>Alternatively, use the <tt>--account</tt> parameter.</p>
  104. <p>You get a dialog box where for me, I use TouchID to sign in.</p>
  105. <p><tt>op signin</tt> is idempotent so is a no-op if the Terminal is already signed in.</p>
  106. <h2>Access data</h2>
  107. <p>There are multiple ways to retrieve the data from a 1Password item.</p>
  108. <h3><tt style="color:inherit">op item get</tt></h3>
  109. <p>Use <tt>op item get "&lt;item&gt;" --field "&lt;fieldname&gt;"</tt> to get a specific field. e.g</p>
  110. <pre>op item get "Apple App Notarisation" --field "username"</pre>
  111. <p>The <tt>&lt;item&gt;</tt> can be the name of the item or its id. e.g. something like <tt>dajka2z5l57m4p43s6bapd3eo4</tt></p>
  112. <p>Note, that for a password, you also need to pass in <tt>--reveal</tt>.</p>
  113. <p>As I'm writing a script, I assign to a variable:</p>
  114. <pre>
  115. APPLE_ID=$(op item get "Apple App Notarisation" --field username)
  116. APP_SPECIFIC_PASSWORD=$(op item get "Apple App Notarisation" --field password --reveal)
  117. TEAM_ID=$(op item get "Apple App Notarisation" --field team_id)
  118. </pre>
  119. <p>Alternatively, you can get back multiple fields in one go by providing a list of comma separated fields:</p>
  120. <pre>
  121. FIELDS=$(op item get "Apple App Notarisation" --fields username,password,team_id --reveal)
  122. IFS=',' read -r APPLE_ID APP_SPECIFIC_PASSWORD TEAM_ID <<< "$FIELDS"
  123. </pre>
  124. <h3><tt style="color:inherit">op read</tt></h3>
  125. <p>You can also use the <tt>read parameter</tt> which takes a URL-style path:</p>
  126. <pre>
  127. op read op://&lt;vault&gt;/&lt;item&gt;/&lt;field&gt;
  128. </pre>
  129. <p>Use <tt>op vault list</tt> to view the list of vault names and you don't need <tt>--reveal</tt> for passwords. </p>
  130. <p>For my case, I can use:</p>
  131. <pre>
  132. APPLE_ID=$(read "op://Private/Apple App Notarisation/username")
  133. APP_SPECIFIC_PASSWORD=$(op read "op://Private/Apple App Notarisation/password")
  134. TEAM_ID=$(op read "op://Private/Apple App Notarisation/team_id")
  135. </pre>
  136. <h3>Format as JSON</h3>
  137. <p>You can also get the entire item in JSON using:</p>
  138. <pre>op item get "Apple App Notarisation" --format json</pre>
  139. <p>Then use <a href="https://jqlang.org/"><tt>jq</tt></a> to extract what you need. e.g to print the username and password you could do:</p>
  140. <pre>op item get "Apple App Notarisation" --format json | jq -r '
  141.  .fields[] | select(.label=="username" or .label=="password") | "\(.label): \(.value)"
  142. '</pre>
  143. <h3>That's it</h3>
  144. <p>That's it. Very simple to put into a script and keep my password secure.</p>
  145. ]]></content:encoded>
  146. <wfw:commentRss>https://akrabat.com/using-the-1password-cli-in-a-script/feed/</wfw:commentRss>
  147. <slash:comments>0</slash:comments>
  148. </item>
  149. <item>
  150. <title>Accessing my printer&#039;s web app remotely via Tailscale</title>
  151. <link>https://akrabat.com/accessing-my-printers-web-app-remotely-via-tailscale/</link>
  152. <comments>https://akrabat.com/accessing-my-printers-web-app-remotely-via-tailscale/#respond</comments>
  153. <dc:creator><![CDATA[Rob]]></dc:creator>
  154. <pubDate>Tue, 01 Jul 2025 09:00:00 +0000</pubDate>
  155. <category><![CDATA[Computing]]></category>
  156. <guid isPermaLink="false">https://akrabat.com/?p=7404</guid>
  157.  
  158. <description><![CDATA[We have an HP all-in-one scanner and printer that is on our local network. Recently, I was away from home and needed to reconfigure the scanning settings for unimportant reasons. Usually, when I'm not in the office, I use Tailscale to connect back to machines as required, but the printer isn't running Tailscale, so its built-in web app isn't directly available. To solve this problem, I set up Tailscale subnet route on the Linux box… <a href="https://akrabat.com/accessing-my-printers-web-app-remotely-via-tailscale/">continue reading</a>.]]></description>
  159. <content:encoded><![CDATA[<p>We have an HP all-in-one scanner and printer that is on our local network. Recently, I was away from home and needed to reconfigure the scanning settings for unimportant reasons.</p>
  160. <p>Usually, when I'm not in the office, I use <a href="https://tailscale.com/">Tailscale</a> to connect back to machines as required, but the printer isn't running Tailscale, so its built-in web app isn't directly available. To solve this problem, I set up Tailscale subnet route on the Linux box I have in the office.</p>
  161. <p>As this is Linux box, it was easy enough to SSH into it and I ran:</p>
  162. <pre>
  163. sudo tailscale set --advertise-routes=192.168.220./24
  164. </pre>
  165. <p>I can't remember why our local network uses the 192.168.220.0 range; I suspect it's related to a client's VPN config being overbearing, before I started jailing such clients in a VM.</p>
  166. <p>The Linux box now knows about the subnet route. To enable it, head over the <a href="https://login.tailscale.com/admin/machines">machines tab</a> of the Tailscale admin, find the machine in question and click the "&#8230;" button to find the "Edit route settings" item to authorise it.</p>
  167. <p>Once this was done, Chrome on my remote Mac could access the printer's web app on https://192.168.220.24 and I did the admin required remotely.</p>
  168. <p>I'm unclear if there'll be any issues leaving this route enabled all the time, as <a href="https://github.com/tailscale/tailscale/issues/1227">issue 1227</a> implies that everything local might get routed via my Linux box, so I disabled it after use and will enable it as an when I need it.</p>
  169. ]]></content:encoded>
  170. <wfw:commentRss>https://akrabat.com/accessing-my-printers-web-app-remotely-via-tailscale/feed/</wfw:commentRss>
  171. <slash:comments>0</slash:comments>
  172. </item>
  173. <item>
  174. <title>FIxing Linux Tailscale exit node routing</title>
  175. <link>https://akrabat.com/fixing-linux-tailscale-exit-node-routing/</link>
  176. <comments>https://akrabat.com/fixing-linux-tailscale-exit-node-routing/#comments</comments>
  177. <dc:creator><![CDATA[Rob]]></dc:creator>
  178. <pubDate>Tue, 24 Jun 2025 09:00:00 +0000</pubDate>
  179. <category><![CDATA[Computing]]></category>
  180. <guid isPermaLink="false">https://akrabat.com/?p=7401</guid>
  181.  
  182. <description><![CDATA[I run a Tailscale network so that remote computers can access local services. I also have a Linux box at home on that network that advertises itself as an exit node and recently noticed that it wasn't working. I had some time recently to sit down and work out what was going on. My initial suspicion was that it was DNS related as a cursory search brought up lots of results related to DNS. However,… <a href="https://akrabat.com/fixing-linux-tailscale-exit-node-routing/">continue reading</a>.]]></description>
  183. <content:encoded><![CDATA[<p>I run a <a href="https://tailscale.com/">Tailscale</a> network so that remote computers can access local services. I also have a Linux box at home on that network that advertises itself as an <a href="https://tailscale.com/kb/1103/exit-nodes">exit node</a> and recently noticed that it wasn't working.</p>
  184. <p>I had some time recently to sit down and work out what was going on. My initial suspicion was that it was DNS related as a cursory search brought up lots of results related to DNS. However, some quick tests with <tt>nslookup</tt> and <tt>dig</tt> showed that DNS was correctly resolving, so it seemed to be a routing issue.</p>
  185. <p>Further searching led me to realise that my Linux box needs to masquerade the traffic. This can be done using:</p>
  186. <pre>
  187. sudo iptables -t nat -A POSTROUTING -o {network interface} -j MASQUERADE
  188. </pre>
  189. <p>I used <tt>ifconfig</tt> to look up my network interface which was <tt>enp3s0</tt> and then all was well. </p>
  190. <p>I connected my Mac to the exit node from a remote location and could browse the web with my remote IP address correctly set to my home's IP address.</p>
  191. <p>Given that this was working, I'm unclear what has changed such that this setting needed configuring. I have found <a href="https://github.com/tailscale/tailscale/issues/15708">issue 15708</a> which may be related so potentially a future Tailscale update will solve this. I don't rebook this box often, so maybe I set this flag before and forgot?</p>
  192. <p>I've written it up here now though, so I can find it again if I need it!</p>
  193. ]]></content:encoded>
  194. <wfw:commentRss>https://akrabat.com/fixing-linux-tailscale-exit-node-routing/feed/</wfw:commentRss>
  195. <slash:comments>1</slash:comments>
  196. </item>
  197. <item>
  198. <title>Renaming files with Hazel</title>
  199. <link>https://akrabat.com/renaming-files-with-hazel/</link>
  200. <comments>https://akrabat.com/renaming-files-with-hazel/#respond</comments>
  201. <dc:creator><![CDATA[Rob]]></dc:creator>
  202. <pubDate>Tue, 17 Jun 2025 10:00:00 +0000</pubDate>
  203. <category><![CDATA[Mac]]></category>
  204. <guid isPermaLink="false">https://akrabat.com/?p=7390</guid>
  205.  
  206. <description><![CDATA[I'm a member of a number of groups that publish a magazine, either paper-based or PDF. I prefer the PDF version, so download from the website and then move to the relevant directory. Recently, I realised that I could use Hazel to do this for me. To take one example, the filename of the PDF that I download is of the format PE-{issue number}-web{some random characters}.pdf, for example: PE-123-web9j45s3gd.pdf. There's sometimes a hyphen after web,… <a href="https://akrabat.com/renaming-files-with-hazel/">continue reading</a>.]]></description>
  207. <content:encoded><![CDATA[<p>I'm a member of a number of groups that publish a magazine, either paper-based or PDF. I prefer the PDF version, so download from the website and then move to the relevant directory.</p>
  208. <p>Recently, I realised that I could use Hazel to do this for me.</p>
  209. <p>To take one example, the filename of the PDF that I download is of the format <tt>PE-{issue number}-web{some random characters}.pdf</tt>, for example: <tt>PE-123-web9j45s3gd.pdf</tt>. There's sometimes a hyphen after <tt>web</tt>, but not always.</p>
  210. <p>I want the filename to <tt>PE Magazine {issue number}</tt></p>
  211. <p>The rule in Hazel looks like this:</p>
  212. <picture><source  
  213. srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-rename-pe-mag-dark.png"
  214.        media="(prefers-color-scheme: dark)"
  215.    /><source
  216. srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-rename-pe-mag-light.png"
  217.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  218.    /><br />
  219.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-rename-pe-mag-light.png" loading="lazy" alt="Screenshot of Hazel rename rule" width="500"/>
  220. </picture>
  221. <h2>Matching the name</h2>
  222. <p>Breaking it down, we detect that this is a file of interest with a <em>Name matches</em> rule. We can start with the literal string <tt>PE-</tt> as that's constant, but then we need to match the number and also give it a name so that we can refer to it later.</p>
  223. <p>This is done using the "Custom Text" element. You can then click on it to edit its attributes where I set its name to "issue" and the pattern to "Number" which matches sequential digits.</p>
  224. <picture><source  
  225. srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-match-attributes-dark.png"
  226.        media="(prefers-color-scheme: dark)"
  227.    /><source
  228. srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-match-attributes-light.png"
  229.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  230.    /><br />
  231.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-match-attributes-light.png" loading="lazy" alt="Screenshot of Hazel match attributes screen" class="border" width="300"/>
  232. </picture>
  233. <p>The rest of the name match is a hyphen followed by an "Anything" element as we don't care about any other characters.</p>
  234. <p><tt>Renaming</tt></p>
  235. <p>Now that we have we can use the <em>Rename with pattern</em> action with the literal "<tt>PE Magazine - </tt>" followed by our "issue" custom element that we created in the match rule, followed by the extension.</p>
  236. <h2>Finishing it off</h2>
  237. <p>Once the file has been renamed, there's a "Move to folder" action to move it the right place and then the folder is opened, so that I can grab the file if I need to.</p>
  238. <p>That's it. Automating the rename and move means that I don't have to think about it again.</p>
  239. ]]></content:encoded>
  240. <wfw:commentRss>https://akrabat.com/renaming-files-with-hazel/feed/</wfw:commentRss>
  241. <slash:comments>0</slash:comments>
  242. </item>
  243. <item>
  244. <title>Updating SMTP relay in postfix</title>
  245. <link>https://akrabat.com/updating-smtp-relay-in-postfix/</link>
  246. <comments>https://akrabat.com/updating-smtp-relay-in-postfix/#comments</comments>
  247. <dc:creator><![CDATA[Rob]]></dc:creator>
  248. <pubDate>Tue, 10 Jun 2025 10:00:00 +0000</pubDate>
  249. <category><![CDATA[Computing]]></category>
  250. <category><![CDATA[Sysadmin]]></category>
  251. <guid isPermaLink="false">https://akrabat.com/?p=7383</guid>
  252.  
  253. <description><![CDATA[On a server that I help to maintain, it has postfix installed for emailing results of cron jobs and other status updates. This was set up to relay through SendGrid as they had a 100 email per month plan and we send out significantly fewer than that. Unfortunately, SendGrid are retiring their free plan, so I had to move to a new service and picked SMTP2GO and so had to update the postfix configuration. As… <a href="https://akrabat.com/updating-smtp-relay-in-postfix/">continue reading</a>.]]></description>
  254. <content:encoded><![CDATA[<p>On a server that I help to maintain, it has <a href="https://www.postfix.org">postfix</a> installed for emailing results of cron jobs and other status updates. This was set up to relay through SendGrid as they had a 100 email per month plan and we send out significantly fewer than that.</p>
  255. <p>Unfortunately, SendGrid are retiring their free plan, so I had to move to a new service and picked <a href="https://www.smtp2go.com">SMTP2GO</a> and so had to update the postfix configuration. As I didn't have this written down, I'm noting in here.</p>
  256. <h2>Get SMTP details</h2>
  257. <p>You need the <tt>SMTP hostname</tt>, <tt>SMTP username</tt> and <tt>SMTP password</tt>. SMTP2GO allows you to have multiple usernames and usefully allows you to rate limit each one individually, should you need that.</p>
  258. <h2>Configure postfix</h2>
  259. <h3>Firstly, update <tt>/etc/postfix/main.cf</tt></h3>
  260. <p>Find <tt>relayhost</tt> and change to <tt>[mail.smtp2go.com]:587</tt>. I'm just updating, so the rest of the settings I left alone. </p>
  261. <p>However, the relevant settings are:</p>
  262. <pre>
  263. relayhost = [mail.smtp2go.com]:587
  264. smtp_sasl_auth_enable = yes
  265. smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
  266. smtp_sasl_security_options = noanonymous
  267. smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
  268. smtp_use_tls = yes
  269. </pre>
  270. <h3>Secondly, add the new SMTP user's credentials</h3>
  271. <p>Edit <tt>/etc/postfix/sasl_passwd</tt>. This needs to be done as root as the permissions on this file are <tt>600</tt> and it's owned by <tt>root:root</tt>.</p>
  272. <p>Add a new line with this format:</p>
  273. <pre>
  274. [mail.smtp2go.com]:587 {username}:{password}
  275. </pre>
  276. <p>Where <tt>{username}</tt> is the SMTP username and <tt>{password}</tt> is the SMTP password.</p>
  277. <p>Save the file and then create the hash database:</p>
  278. <pre>
  279. sudo postmap /etc/postfix/sasl_passwd
  280. </pre>
  281. <h3>Restart postfix</h3>
  282. <p>That's it, so just restart postfix with <tt>sudo service postfix restart</tt></p>
  283. ]]></content:encoded>
  284. <wfw:commentRss>https://akrabat.com/updating-smtp-relay-in-postfix/feed/</wfw:commentRss>
  285. <slash:comments>1</slash:comments>
  286. </item>
  287. <item>
  288. <title>Global word count on Mac using Shortcuts</title>
  289. <link>https://akrabat.com/global-word-count-on-mac-using-shortcuts/</link>
  290. <comments>https://akrabat.com/global-word-count-on-mac-using-shortcuts/#respond</comments>
  291. <dc:creator><![CDATA[Rob]]></dc:creator>
  292. <pubDate>Tue, 03 Jun 2025 10:00:00 +0000</pubDate>
  293. <category><![CDATA[Computing]]></category>
  294. <category><![CDATA[Mac]]></category>
  295. <guid isPermaLink="false">https://akrabat.com/?p=7361</guid>
  296.  
  297. <description><![CDATA[I've had a few cases recently when I wanted to know the number of words that I had written. To do this, I've copied the text to BBEdit which displays the word count in its status bar, but this is a bit of a faff. I finally sat down and created a Shortcut for it that took 10 mins. This is the shortcut: The idea is that I want to select the text and run… <a href="https://akrabat.com/global-word-count-on-mac-using-shortcuts/">continue reading</a>.]]></description>
  298. <content:encoded><![CDATA[<p>I've had a few cases recently when I wanted to know the number of words that I had written. To do this, I've copied the text to BBEdit which displays the word count in its status bar, but this is a bit of a faff.</p>
  299. <p>I finally sat down and created a Shortcut for it that took 10 mins.</p>
  300. <p>This is the shortcut:</p>
  301. <picture><source  
  302. srcset="https://akrabat.com/wp-content/uploads/2025/05/2025-05-word-count-shortcut-light.png"
  303.        media="(prefers-color-scheme: dark)"
  304.    /><source
  305. srcset="https://akrabat.com/wp-content/uploads/2025/05/2025-05-word-count-shortcut-light.png"
  306.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  307.    /><br />
  308.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/05/2025-05-word-count-shortcut-light.png" loading="lazy" alt="Screenshort of Word Count shortcut" width="600"/>
  309. </picture>
  310. <p>The idea is that I want to select the text and run the shortcut without having to copy to the clipboard, so to do this, I enable "Use as Quick Action" on the Services menu. I also assigned a global shortcut of <tt>⌃⌥⇧⌘W</tt> for convenience, though going to the Services menu isn't hard.</p>
  311. <p>A Quick Action shortcut automatically gets a "Receive" input block, which I set to be just accept text input as it makes no sense to count words in other types of text. I also set it to look in the clipboard if there's no selection. Once we have input, we run it through the "Count" action and then pipe to a "Show Alert" so that the value can be seen. It's all quite easy.</p>
  312. <p>That's it. I can now select text and press </p>
  313. <pre>⌃⌥⇧⌘W</pre>
  314. <p> and the number of words is displayed!</p>
  315. <picture><source  
  316. srcset="https://akrabat.com/wp-content/uploads/2025/05/2025-05-wordcount-screenshot-dark.png"
  317.        media="(prefers-color-scheme: dark)"
  318.    /><source
  319. srcset="https://akrabat.com/wp-content/uploads/2025/05/2025-05-wordcount-screenshot-light.png"
  320.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  321.    /><br />
  322.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/05/2025-05-wordcount-screenshot-light.png" loading="lazy" alt="Screenshort of output of Word Count shortcut" width="300"/>
  323. </picture>
  324. ]]></content:encoded>
  325. <wfw:commentRss>https://akrabat.com/global-word-count-on-mac-using-shortcuts/feed/</wfw:commentRss>
  326. <slash:comments>0</slash:comments>
  327. </item>
  328. <item>
  329. <title>Moving a Perforce P4 P4ROOT to a different drive</title>
  330. <link>https://akrabat.com/moving-a-perforce-p4-p4root-to-a-different-drive/</link>
  331. <comments>https://akrabat.com/moving-a-perforce-p4-p4root-to-a-different-drive/#respond</comments>
  332. <dc:creator><![CDATA[Rob]]></dc:creator>
  333. <pubDate>Tue, 27 May 2025 10:00:00 +0000</pubDate>
  334. <category><![CDATA[Software]]></category>
  335. <guid isPermaLink="false">https://akrabat.com/?p=7349</guid>
  336.  
  337. <description><![CDATA[On one of my servers here, I run a local Perforce P4 server for my son. He's a game developer and as they use P4 at work, he wanted to learn it in a sandbox and to have somewhere familiar to put his own work. Installation onto Ubuntu was easy enough and I provided access outside of our local network via Tailscale and all was well. Recently, I was doing some admin-stuff on the server… <a href="https://akrabat.com/moving-a-perforce-p4-p4root-to-a-different-drive/">continue reading</a>.]]></description>
  338. <content:encoded><![CDATA[<p>On one of my servers here, I run a local <a href="https://www.perforce.com/products/helix-core">Perforce P4</a> server for my son. He's a game developer and as they use P4 at work, he wanted to learn it in a sandbox and to have somewhere familiar to put his own work.</p>
  339. <p>Installation onto Ubuntu was easy enough and I provided access outside of our local network via <a href="https://tailscale.com">Tailscale</a> and all was well.</p>
  340. <p>Recently, I was doing some admin-stuff on the server and realised that the drive that P4 was using was the local 256GB drive rather than one of the big 8TB drives in the box, so I needed to move it. The directory that the instance was using was <tt>/opt/perforce/servers/p4d-holland1</tt>. This needed moving to <tt>/media/ssd1/data/perforce/servers/p4d-holland1</tt>.</p>
  341. <p><strong>Note</strong> I run a single instance, very default, not-very-important  P4 server. Don't just copy these steps blindly if your setup is more complicated or mission critical! Make sure that you <a href="https://help.perforce.com/helix-core/server-apps/p4sag/2024.1/Content/P4SAG/moving-same-machine.html">read the docs</a>.</p>
  342. <p>These are the steps I followed:</p>
  343. <ul>
  344. <li>Stop the instance: <tt>p4dctl stop p4d-holland1</tt></li>
  345. <li>Move the data by copying it and renaming the old directory (just in case):
  346. <pre>
  347. mkdir -p /media/ssd1/data/perforce/servers/p4d-holland1
  348. cp -r /opt/perforce/servers/p4d-holland1/* /media/ssd1/data/perforce/servers/p4d-holland1/
  349. mv /opt/perforce/servers/p4d-holland1 /opt/perforce/servers/p4d-holland1-old
  350. </pre>
  351. </li>
  352. <li>Edit <tt>/etc/perforce/p4dctl.conf.d/p4d-holland1.conf</tt> and update the <tt>P4ROOT</tt> to the new directory</li>
  353. <li>Start the instance again: <tt>p4dctl start p4d-holland1</tt></li>
  354. </ul>
  355. <p>I didn't see any errors, so I got my son to check it. After he said it was all good, I removed the old <tt>/opt/perforce/servers/p4d-holland1-old</tt> directory.</p>
  356. <p>My son now has more than enough space to continue his side-projects.</p>
  357. ]]></content:encoded>
  358. <wfw:commentRss>https://akrabat.com/moving-a-perforce-p4-p4root-to-a-different-drive/feed/</wfw:commentRss>
  359. <slash:comments>0</slash:comments>
  360. </item>
  361. <item>
  362. <title>Updated rst2pdf website</title>
  363. <link>https://akrabat.com/updated-rst2pdf-website/</link>
  364. <comments>https://akrabat.com/updated-rst2pdf-website/#respond</comments>
  365. <dc:creator><![CDATA[Rob]]></dc:creator>
  366. <pubDate>Tue, 20 May 2025 10:00:00 +0000</pubDate>
  367. <category><![CDATA[rst2pdf]]></category>
  368. <guid isPermaLink="false">https://akrabat.com/?p=7355</guid>
  369.  
  370. <description><![CDATA[Earlier this year, Lorna spent some time updating rst2pdf's website to use Sphinx. The nice thing about Sphinx is that it uses restructuredText, the same as rst2pdf does, so we now stay in the same ecosystem. While, we could have continued using Jekyll, it makes much more sense for us to use the same markup language as we use for the main tool, and our manual is written in it too. With the Jekyll system,… <a href="https://akrabat.com/updated-rst2pdf-website/">continue reading</a>.]]></description>
  371. <content:encoded><![CDATA[<p>Earlier this year, <a href="https://lornajane.net/about">Lorna</a> spent some time updating <a href="https://rst2pdf.org">rst2pdf</a>'s website to use <a href="https://www.sphinx-doc.org/en/master/">Sphinx</a>. The nice thing about Sphinx is that it uses <a href="https://docutils.sourceforge.io/rst.html">restructuredText</a>, the same as rst2pdf does, so we now stay in the same ecosystem.</p>
  372. <p>While, we could have continued using Jekyll, it makes much more sense for us to use the same markup language as we use for the main tool, and our manual is written in it too. With the Jekyll system, we used docutils with a generic and simple CSS file to create the HTML version of the manual, but now as Sphinx is rST native, it builds the HTML version of the manual and it as it is now styled the same as the website, it looks much better. Of course, we use rst2pdf to generate the PDF version ;)</p>
  373. <p>Rather handily, we now have search on the site and also have a dark mode which you can see in this screenshot if your OS is in dark mode as you view this page. I also like that you can click on the "eye" icon and view the rST source for the page.</p>
  374. <picture><source  
  375. srcset="https://akrabat.com/wp-content/uploads/2025/05/2025-05-rst2pdf.org-dark.png"
  376.        media="(prefers-color-scheme: dark)"
  377.    /><source
  378. srcset="https://akrabat.com/wp-content/uploads/2025/05/2025-05-rst2pdf.org-light.png"
  379.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  380.    /><br />
  381.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/05/2025-05-rst2pdf.org-light.png" loading="lazy" alt="2025 05 rst2pdf.org light." width="600"/>
  382. </picture>
  383. <p>This required a fair amount of work. Thanks Lorna for taking the time to do it!</p>
  384. ]]></content:encoded>
  385. <wfw:commentRss>https://akrabat.com/updated-rst2pdf-website/feed/</wfw:commentRss>
  386. <slash:comments>0</slash:comments>
  387. </item>
  388. <item>
  389. <title>Using OneOf for a property in an OpenAPI spec</title>
  390. <link>https://akrabat.com/using-oneof-for-a-property-in-an-openapi-spec/</link>
  391. <comments>https://akrabat.com/using-oneof-for-a-property-in-an-openapi-spec/#respond</comments>
  392. <dc:creator><![CDATA[Rob]]></dc:creator>
  393. <pubDate>Tue, 13 May 2025 10:00:00 +0000</pubDate>
  394. <category><![CDATA[API]]></category>
  395. <category><![CDATA[OpenAPI]]></category>
  396. <guid isPermaLink="false">https://akrabat.com/?p=7330</guid>
  397.  
  398. <description><![CDATA[When writing an OpenAPI specification, I came across the need for a particular property in an error response to be either a string or an object. This situation came about when validating a POST request that takes an items property that is a list of objects As a contrived example of a pet with accessories, consider this example request in curl: curl -X "POST" "http://api.example.com/pets" \ -H 'Content-Type: application/json' \ -d $'{ "name": "Rover", "items": [… <a href="https://akrabat.com/using-oneof-for-a-property-in-an-openapi-spec/">continue reading</a>.]]></description>
  399. <content:encoded><![CDATA[<p>When writing an <a href="https://www.openapis.org">OpenAPI specification</a>, I came across the need for a particular property in an error response to be either a string or an object.</p>
  400. <p>This situation came about when validating a POST request that takes an <tt>items</tt> property that is a list of objects</p>
  401. <p>As a contrived example of a pet with accessories, consider this example request in <tt>curl</tt>:</p>
  402. <pre>
  403. curl -X "POST" "http://api.example.com/pets" \
  404.     -H 'Content-Type: application/json' \
  405.     -d $'{
  406.  "name": "Rover",
  407.  "items": [
  408.    {
  409.      "name": "collar"
  410.      "count": 1
  411.    },
  412.    {
  413.      "name": "bowl"
  414.      "count": 2
  415.    }
  416.  ],
  417. }'
  418. </pre>
  419. <p>For the <tt>items</tt> property, there are two error responses if validation fails:</p>
  420. <ol>
  421. <li>Property missing or wrong type.<br />For example:
  422. <pre>
  423. {
  424.  "items": "Items property is required"
  425. }
  426. </pre>
  427. </li>
  428. <li>Missing or invalid sub property<br />For example on the <tt>count</tt> property of the second item:
  429. <pre>
  430. {
  431.  "items": [
  432.    {
  433.    },
  434.    {
  435.        "count": "Item count must be a positive integer"
  436.    }
  437.  ]
  438. }
  439. </pre>
  440. </li>
  441. </ol>
  442. <p>To document this in OpenAPI, I wrote the following for my <tt>'400'</tt> response (simplified):</p>
  443. <pre>
  444. '400':
  445.  description: Validation failed
  446.  content:
  447.      application/json:
  448.        schema:
  449.          type: object
  450.          properties:
  451.            errors:
  452.              type: object
  453.              properties:
  454.                name:
  455.                  type: string
  456.                  example: "Pet name must not be empty"
  457.                items:
  458.                  oneOf:
  459.                    - $ref: '#/components/schemas/ValidationErrorPetItemsObject'
  460.                    - $ref: '#/components/schemas/ValidationErrorPetItemsString'
  461.  
  462. </pre>
  463. <p>Within the <tt>components</tt> -&gt; <tt>schemas</tt> section, we define both schemas:</p>
  464. <pre>
  465. ValidationErrorPetItemsString:
  466.  type: string
  467.  example: "Items property is required"
  468.  
  469.  
  470. ValidationErrorPetItemsPropertyObject:
  471.  type: array
  472.  items:
  473.    type: object
  474.    properties:
  475.      id:
  476.        type: string
  477.        example: "Item name is required"
  478.      count:
  479.        type: string
  480.        example: "Item count must be a positive integer"
  481. </pre>
  482. <p>I don't know how to specify that an empty array will be included so that the client can work out which item object has the problem, so for now it is documented in the description.</p>
  483. <p>There's probably other ways to solve this, but this is the one I came up with. If there's a better way, let me know.</p>
  484. ]]></content:encoded>
  485. <wfw:commentRss>https://akrabat.com/using-oneof-for-a-property-in-an-openapi-spec/feed/</wfw:commentRss>
  486. <slash:comments>0</slash:comments>
  487. </item>
  488. </channel>
  489. </rss>
  490.  

If you would like to create a banner that links to this page (i.e. this validation result), do the following:

  1. Download the "valid RSS" banner.

  2. Upload the image to your own server. (This step is important. Please do not link directly to the image on this server.)

  3. Add this HTML to your page (change the image src attribute if necessary):

If you would like to create a text link instead, here is the URL you can use:

http://www.feedvalidator.org/check.cgi?url=http%3A//akrabat.com/feed/

Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda