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 today&#039;s world</description>
  15. <lastBuildDate>Tue, 19 Aug 2025 12:30:31 +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>Converting JWKS JSON to PEM using Python</title>
  24. <link>https://akrabat.com/converting-jwks-json-to-pem-using-python/</link>
  25. <comments>https://akrabat.com/converting-jwks-json-to-pem-using-python/#respond</comments>
  26. <dc:creator><![CDATA[Rob]]></dc:creator>
  27. <pubDate>Tue, 09 Sep 2025 10:00:00 +0000</pubDate>
  28. <category><![CDATA[Command Line]]></category>
  29. <category><![CDATA[Python]]></category>
  30. <guid isPermaLink="false">https://akrabat.com/?p=7484</guid>
  31.  
  32. <description><![CDATA[Following on from my earlier exploration of JWKS (RFC7517), I found myself needing to convert the JWKS into PEM format. This time I turned to Python with my preference of using uv with inline script metadata and created jwks-to-pem.py. The really nice thing about inline script metadata is that we can use the cryptography package to do all the hard work with RSA and serialisation. We just have to remember that the base64 encoded values… <a href="https://akrabat.com/converting-jwks-json-to-pem-using-python/">continue reading</a>.]]></description>
  33. <content:encoded><![CDATA[<p>Following on from my <a href="https://akrabat.com/creating-jwks-json-file-in-php/">earlier exploration of JWKS</a> (<a href="https://www.rfc-editor.org/rfc/rfc7517">RFC7517</a>), I found myself needing to convert the JWKS into PEM format.</p>
  34. <p>This time I turned to Python with my preference of using <a href="https://github.com/astral-sh/uv">uv</a> with <a href="https://akrabat.com/defining-python-dependencies-at-the-top-of-the-file/">inline script metadata</a> and created <a href="#script"><tt>jwks-to-pem.py</tt></a>.</p>
  35. <p>The really nice thing about inline script metadata is that we can use the <a href="https://pypi.org/project/cryptography/">cryptography package</a> to do all the hard work with RSA and serialisation. We just have to remember that the base64 encoded values are <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">base64 URL encoded</a> and account for it.</p>
  36. <p>As a single file python script, I make it executable with <tt>chmod +x jwks-to-pem.py</tt> and made it so that I can pipe the output of a <a href="https://curl.se">curl</a> call to it, or pass in a JSON file. I prefer to use the <tt>curl</tt> solution though with:</p>
  37. <pre>
  38. curl -s https://example.com/.well-known/jwks.json | jwks-to-pem.py
  39. </pre>
  40. <h3>Example</h3>
  41. <p>Here's an example from the <a href="https://developer.api.apps.cam.ac.uk/docs/oauth2/1/routes/.well-known/jwks.json/get">University of Cambridge</a>.</p>
  42. <p>On the day I wrote this article, the JWKS looks like this:</p>
  43. <pre>
  44. $ curl -s https://api.apps.cam.ac.uk/oauth2/v1/.well-known/jwks.json
  45. {
  46.  "keys": [
  47.    {
  48.      "alg": "RS256",
  49.      "e": "AQAB",
  50.      "n": "6kKjjctVPalX0ypJ2irwog8xIXS9JTABqrSnK_n3YJ4q0aH2-1bjGbWz8p1CaCUqDxQSDuqzvOgMdNGvZrxlNJ-G8hfc39jrb_KnB0T3ZsuxFz6X0mDzmHhdiPjSDK3M0syC4qg5_PB7xwKail5VWOcY0SypIYCPD6Ct5DGnQ_XONGXIVG7eaJAHdxJp2BOz0n3BVEFnZUgM5JcfGrSFfGqb0ZotX2AblwjZKQc58E0EVVykJw8gxW1Bob8rbaVXlMHssfY-9Jx0zua7ZrjO5C4OMmt9J6zYbVnGVwf62ehGtcLSP6iCG4_XM2sAMQwqJnPBss0U9WwDERk17FMHvb_FBwxAFxRygd0DclWmQmCYr5uFYck57KGARtyoxrNNAf4AFUHuObjbV24TyInYEgMhKi3SAML_4ke3dbbG-mjchXPN9OqNd4fydnQIP39WFHmFNk_nIlqvYnALI4xPE-w09T9jCvjU8hYHHlVMRvRluBnUzJkFnxLse5W-agC6ITe3wYvKH7SHVp6MYQWVD_0I2rCLV4gqjSpXzKIMs5eejjTQQq0VYumgL_f1ETvzDoewzXLOC8GGu2LZDwDbP0ea6DchReWjZfj4nJx23uQyGAj1h_uPI1jCd9oeJhbN8jFz2ltYgXYBp51qdSzsbtdec9BPPBVeXjI--c0AWU8",
  51.      "kid": "70e0ed3c",
  52.      "kty": "RSA",
  53.      "use": "sig"
  54.    }
  55.  ]
  56. }
  57. </pre>
  58. <p>They very kindly pretty-print it too!</p>
  59. <p>We can then get the PEM version by piping to <tt>jwks-to-pem.py</tt>:</p>
  60. <pre>
  61. $ curl -s https://api.apps.cam.ac.uk/oauth2/v1/.well-known/jwks.json | jwks-to-pem.py
  62. # Key 0 (kid: 70e0ed3c)
  63. -----BEGIN PUBLIC KEY-----
  64. MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA6kKjjctVPalX0ypJ2irw
  65. og8xIXS9JTABqrSnK/n3YJ4q0aH2+1bjGbWz8p1CaCUqDxQSDuqzvOgMdNGvZrxl
  66. NJ+G8hfc39jrb/KnB0T3ZsuxFz6X0mDzmHhdiPjSDK3M0syC4qg5/PB7xwKail5V
  67. WOcY0SypIYCPD6Ct5DGnQ/XONGXIVG7eaJAHdxJp2BOz0n3BVEFnZUgM5JcfGrSF
  68. fGqb0ZotX2AblwjZKQc58E0EVVykJw8gxW1Bob8rbaVXlMHssfY+9Jx0zua7ZrjO
  69. 5C4OMmt9J6zYbVnGVwf62ehGtcLSP6iCG4/XM2sAMQwqJnPBss0U9WwDERk17FMH
  70. vb/FBwxAFxRygd0DclWmQmCYr5uFYck57KGARtyoxrNNAf4AFUHuObjbV24TyInY
  71. EgMhKi3SAML/4ke3dbbG+mjchXPN9OqNd4fydnQIP39WFHmFNk/nIlqvYnALI4xP
  72. E+w09T9jCvjU8hYHHlVMRvRluBnUzJkFnxLse5W+agC6ITe3wYvKH7SHVp6MYQWV
  73. D/0I2rCLV4gqjSpXzKIMs5eejjTQQq0VYumgL/f1ETvzDoewzXLOC8GGu2LZDwDb
  74. P0ea6DchReWjZfj4nJx23uQyGAj1h/uPI1jCd9oeJhbN8jFz2ltYgXYBp51qdSzs
  75. btdec9BPPBVeXjI++c0AWU8CAwEAAQ==
  76. -----END PUBLIC KEY-----
  77.  
  78. </pre>
  79. <h3>The script</h3>
  80. <p>This is the script in case anyone else finds it useful:</p>
  81. <pre lang="python" id="script">
  82. #!/usr/bin/env -S uv run --script --quiet
  83. # /// script
  84. # dependencies = [
  85. #   "cryptography",
  86. # ]
  87. # ///
  88.  
  89. """Convert JWK keys to PEM format.
  90.  
  91. This script reads .well-known/jwks.json and outputs PEM encoded versions
  92. of the public keys in that file.
  93.  
  94. Usage:
  95.    curl -s https://example.com/.well-known/jwks.json | jwks-to-pem.py
  96.    uv run jwks-to-pem.py jwks.json
  97.    uv run jwks-to-pem.py < jwks.json
  98.  
  99. Requirements:
  100.    - uv (https://github.com/astral-sh/uv)
  101.    - cryptography library
  102.  
  103. Author:
  104.    Rob Allen <rob@akrabat.com>
  105.    Copyright 2025
  106.  
  107. License:
  108.    MIT License - https://opensource.org/licenses/MIT
  109. """
  110.  
  111. import json
  112. import base64
  113. import sys
  114. from cryptography.hazmat.primitives import serialization
  115. from cryptography.hazmat.primitives.asymmetric import rsa
  116.  
  117. def base64url_decode(data):
  118.    """Decode base64url to bytes"""
  119.    # Add padding if needed
  120.    padding = 4 - len(data) % 4
  121.    if padding != 4:
  122.        data += '=' * padding
  123.  
  124.    # Replace URL-safe chars
  125.    data = data.replace('-', '+').replace('_', '/')
  126.  
  127.    # Decode
  128.    return base64.b64decode(data)
  129.  
  130. def jwk_to_pem(jwk_key):
  131.    """Convert JWK to PEM format"""
  132.    if jwk_key['kty'] != 'RSA':
  133.        raise ValueError("Only RSA keys are supported")
  134.  
  135.    # Decode the modulus (n) and exponent (e) to int
  136.    n = int.from_bytes(base64url_decode(jwk_key['n']), 'big')
  137.    e = int.from_bytes(base64url_decode(jwk_key['e']), 'big')
  138.  
  139.    # Create RSA public key
  140.    public_key = rsa.RSAPublicNumbers(e, n).public_key()
  141.  
  142.    # Serialize to PEM
  143.    pem = public_key.public_bytes(
  144.        encoding=serialization.Encoding.PEM,
  145.        format=serialization.PublicFormat.SubjectPublicKeyInfo
  146.    )
  147.    return pem.decode()
  148.  
  149. def main():
  150.    if len(sys.argv) > 2:
  151.        print("Usage: jwk_to_pem.py [jwks.json]")
  152.        print("If no file is provided, reads from stdin")
  153.        sys.exit(1)
  154.  
  155.    if len(sys.argv) == 2 and sys.argv[1] != '-':
  156.        # Read from file
  157.        with open(sys.argv[1], 'r') as f:
  158.            jwks = json.load(f)
  159.    else:
  160.        # Read from stdin
  161.        jwks = json.load(sys.stdin)
  162.  
  163.    # Convert each key
  164.    for i, key in enumerate(jwks['keys']):
  165.        kid = key.get('kid', f'key-{i}')
  166.        print(f"# Key {i} (kid: {kid})")
  167.        print(jwk_to_pem(key))
  168.  
  169. if __name__ == "__main__":
  170.    main()
  171. </pre>
  172. ]]></content:encoded>
  173. <wfw:commentRss>https://akrabat.com/converting-jwks-json-to-pem-using-python/feed/</wfw:commentRss>
  174. <slash:comments>0</slash:comments>
  175. </item>
  176. <item>
  177. <title>Stop in-place editing of bash history items</title>
  178. <link>https://akrabat.com/stop-in-place-editing-of-bash-history-items/</link>
  179. <comments>https://akrabat.com/stop-in-place-editing-of-bash-history-items/#comments</comments>
  180. <dc:creator><![CDATA[Rob]]></dc:creator>
  181. <pubDate>Tue, 02 Sep 2025 10:00:00 +0000</pubDate>
  182. <category><![CDATA[Command Line]]></category>
  183. <category><![CDATA[Shell Scripting]]></category>
  184. <guid isPermaLink="false">https://akrabat.com/?p=7480</guid>
  185.  
  186. <description><![CDATA[Recently, since getting a new computer, I've noticed that I've been losing bash history items and it took a while to work out what was going on, though I'm still not completely sure as it never seemed to be so much of a problem. I regularly use the up and down keys with context specific history. For example, I will type ma and then press up to search back through all the make commands I've… <a href="https://akrabat.com/stop-in-place-editing-of-bash-history-items/">continue reading</a>.]]></description>
  187. <content:encoded><![CDATA[<p>Recently, since getting a new computer, I've noticed that I've been losing bash history items and it took a while to work out what was going on, though I'm still not completely sure as it never seemed to be so much of a problem. </p>
  188. <p>I regularly use the up and down keys with <a href="/context-specific-history-at-the-bash-prompt/">context specific history</a>. For example, I will type <tt>ma</tt> and then press up to search back through all the <tt>make</tt> commands I've used recently and then press enter to run it. </p>
  189. <p>Sometimes, I'll realise that I don't actually want this command and edit it and press enter. Sometimes I'll decide halfway through editing that really I should use a <tt>docker compose</tt> command instead and I'll just back out of my edit via some key stroke that works. I'm not sure what I do here though, probably up/down, or maybe ctrl+c. Whatever I do, sometimes, the history for that line is now my edited mess and not the original command. Then later, when I go to try and find it via the up arrow, it's missing.</p>
  190. <p>This happened infrequently enough that I thought I was misremembering what was in the history, or that maybe it was another tab I was thinking about.</p>
  191. <p>I never want the bash history to be editable; if I cancel out, then I want it back to what it was.</p>
  192. <h2>Fixing with revert-all-at-newline</h2>
  193. <p>This finally annoyed me enough that I sat down with the Internet to work out how to fix it with the <tt>revert-all-at-newline</tt> setting.</p>
  194. <p>The revert-all-at-newline option in bash controls whether readline reverts any changes made to a history line when you press Enter. Note that this is part of readline's behavior, so it affects command line editing in bash and other programs that use <a href="https://en.wikipedia.org/wiki/GNU_Readline">readline</a>.</p>
  195. <p>The simplest thing is to add this to <tt>.bashrc</tt>:</p>
  196. <pre lang="bash">
  197. bind 'set revert-all-at-newline on'
  198. </pre>
  199. <p>Alternatively, you can create a <tt>.inputrc</tt> file with this in it:</p>
  200. <pre>
  201. $include /etc/inputrc
  202. set revert-all-at-newline on
  203. </pre>
  204. <p>To view the current value of <tt>revert-all-at-newline</tt>, use: </p>
  205. <pre>
  206. bind -V | grep revert-all-at-newline
  207. </pre>
  208. <p>It solved my problem, and I've not yet found a case when I want it set the other way.</p>
  209. ]]></content:encoded>
  210. <wfw:commentRss>https://akrabat.com/stop-in-place-editing-of-bash-history-items/feed/</wfw:commentRss>
  211. <slash:comments>1</slash:comments>
  212. </item>
  213. <item>
  214. <title>Extending an OpenAPI Component Schema</title>
  215. <link>https://akrabat.com/extending-an-openapi-component-schema/</link>
  216. <comments>https://akrabat.com/extending-an-openapi-component-schema/#respond</comments>
  217. <dc:creator><![CDATA[Rob]]></dc:creator>
  218. <pubDate>Tue, 26 Aug 2025 10:00:00 +0000</pubDate>
  219. <category><![CDATA[OpenAPI]]></category>
  220. <guid isPermaLink="false">https://akrabat.com/?p=7477</guid>
  221.  
  222. <description><![CDATA[One project that I'm working on uses RFC 9457 Problem Details for HTTP APIs for its error responses. In the OpenAPI spec, we can define this as a component and use in the relevant paths as appropriate: components: schemas: ProblemDetails: type: object properties: type: type: string format: uri-reference description: A URI reference that identifies the problem type default: about:blank example: https://example.com/probs/out-of-credit title: type: string description: A short, human-readable summary of the problem type example: You… <a href="https://akrabat.com/extending-an-openapi-component-schema/">continue reading</a>.]]></description>
  223. <content:encoded><![CDATA[<p>One project that I'm working on uses <a href="https://www.rfc-editor.org/rfc/rfc9457.html">RFC 9457 Problem Details for HTTP APIs </a> for its error responses.</p>
  224. <p>In the OpenAPI spec, we can define this as a component and use in the relevant paths as appropriate:</p>
  225. <pre lang="yaml">
  226. components:
  227.  schemas:
  228.    ProblemDetails:
  229.      type: object
  230.      properties:
  231.        type:
  232.          type: string
  233.          format: uri-reference
  234.          description: A URI reference that identifies the problem type
  235.          default: about:blank
  236.          example: https://example.com/probs/out-of-credit
  237.        title:
  238.          type: string
  239.          description: A short, human-readable summary of the problem type
  240.          example: You do not have enough credit.
  241.        status:
  242.          type: integer
  243.          format: int32
  244.          description: The HTTP status code for this occurrence of the problem
  245.          minimum: 100
  246.          maximum: 599
  247.          example: 403
  248.        detail:
  249.          type: string
  250.          description: A human-readable explanation specific to this occurrence of the problem
  251.          example: Your current balance is 30, but that costs 50.
  252.        instance:
  253.          type: string
  254.          format: uri-reference
  255.          description: A URI reference that identifies the specific occurrence of the problem
  256.          example: /account/12345/msgs/abc
  257.      additionalProperties: true
  258. </pre>
  259. <p>When we return a validation error, we add an <tt>errors</tt> property. Rather than repeating the <tt>ProblemDetails</tt> properties into <tt>ValidationError</tt>, we can add the <tt>errors</tt> using <a href="https://json-schema.org/understanding-json-schema/reference/combining#allOf"><tt>allOf</tt></a>:</p>
  260. <pre lang="yaml">
  261.    ValidationError:
  262.      allOf:
  263.        - $ref: '#/components/schemas/ProblemDetails'
  264.        - type: object
  265.          properties:
  266.            errors:
  267.              type: object
  268.              description: Field-specific validation error messages
  269.              additionalProperties:
  270.                type: string
  271.              example:
  272.                name: "name must be provided"
  273.                dateOfBirth: "date must be in the past"
  274. </pre>
  275. <p>This is quite handy!</p>
  276. ]]></content:encoded>
  277. <wfw:commentRss>https://akrabat.com/extending-an-openapi-component-schema/feed/</wfw:commentRss>
  278. <slash:comments>0</slash:comments>
  279. </item>
  280. <item>
  281. <title>Saving the current URL to a Note</title>
  282. <link>https://akrabat.com/saving-the-current-url-to-a-note/</link>
  283. <comments>https://akrabat.com/saving-the-current-url-to-a-note/#respond</comments>
  284. <dc:creator><![CDATA[Rob]]></dc:creator>
  285. <pubDate>Tue, 19 Aug 2025 10:00:00 +0000</pubDate>
  286. <category><![CDATA[Mac]]></category>
  287. <category><![CDATA[Shortcuts]]></category>
  288. <guid isPermaLink="false">https://akrabat.com/?p=7467</guid>
  289.  
  290. <description><![CDATA[Inspired by John Gruber mentioning on the Cortex podcast that he has a shortcut that saves links to a note in Tot, I thought I'd do something similar for saving to a note in Apple Notes. I want to store as a bullet item containing the name of the page, the link and the date. Something like this: (Funny that the spellchecker doesn't know that Thu is the short form for Thursday) The Save Links… <a href="https://akrabat.com/saving-the-current-url-to-a-note/">continue reading</a>.]]></description>
  291. <content:encoded><![CDATA[<p>Inspired by John Gruber mentioning on the <a href="https://www.relay.fm/cortex/169">Cortex podcast</a> that he has a shortcut that saves links to a note in <a href="https://tot.rocks">Tot</a>, I thought I'd do something similar for saving to a note in Apple Notes.</p>
  292. <p>I want to store as a bullet item containing the name of the page, the link and the date. Something like this:</p>
  293. <picture><source  
  294. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025saved-link-text-dark.png"
  295.        media="(prefers-color-scheme: dark)"
  296.    /><source
  297. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025saved-link-text-light.png"
  298.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  299.    /><br />
  300.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025saved-link-text-light.png" loading="lazy" alt="Saved link text light." class="border" width="408"/>
  301. </picture>
  302. <p>(Funny that the spellchecker doesn't know that Thu is the short form for Thursday)</p>
  303. <h2>The <em>Save Links to Notes</em> Shortcut</h2>
  304. <p>This is the shortcut that I created to do it:
  305. <picture><source  
  306. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-shortcut-dark.png"
  307.        media="(prefers-color-scheme: dark)"
  308.    /><source
  309. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-shortcut-light.png"
  310.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  311.    /><br />
  312.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-shortcut-light.png" loading="lazy" alt="Save links to notes shortcut light." class="noborder" width="558"/>
  313. </picture>
  314. <p>You can download it here:<br />
  315. <a href="https://www.icloud.com/shortcuts/6c12e888f9da431d9977985b229d636b">https://www.icloud.com/shortcuts/6c12e888f9da431d9977985b229d636b</a></p>
  316. <h2>Breaking down the actions</h2>
  317. <p>To get the URL into the shortcut, we want:</p>
  318. <ul>
  319. <li><em>Show in Share Sheet</em> so that it's available on iOS/iPadOS</li>
  320. <li><em>Receive What's Onscreen</em> so that when a browser is focussed on Mac, it finds the URL</li>
  321. <li><em>Use as a Quick Action</em> so that we can assign a keyboard shortcut (<tt style="font-family: sans-serif">⌃⌥⌘U</tt> in case)</li>
  322. </ul>
  323. <p>We can then use <em>Get Contents of web page</em> along with <em>Get Details of Safari Web Page</em> to get the pages's title which Shortcuts calls <em>Name</em> for some reason.</p>
  324. <p>There's an action for <em>Current Date</em>, so we add that to get the variable.</p>
  325. <p>Creating formatted text in a note is a little involved. Firstly we use a <em>Text</em> action to set out the Markdown that we want. I used the date format <tt>EEE, dd MMM yyyy</tt> as it's short and clear to me.</p>
  326. <p>There's a <em>Make Rich Text from Markdown</em> action which processes the Markdown for us, but if you just append it to the note, it doesn't work. The workaround is to add it to a <em>List</em> action and then append the list to the note. </p>
  327. <h2>That's it</h2>
  328. <p>All we need to do now is show a notification including the <tt>Shortcut Input</tt> variable as that's the URL that we've just saved.
  329. <picture><source  
  330. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-notification-dark.png"
  331.        media="(prefers-color-scheme: dark)"
  332.    /><source
  333. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-notification-light.png"
  334.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  335.    /><br />
  336.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-notification-light.png" loading="lazy" alt="Save links to notes notification light." class="noborder" width="376"/>
  337. </picture>
  338. <p>With this shortcut, I can add a new entry to my note from both my Mac, iPad and iPhone with minimal effort. </p>
  339. <p>I like it.</p>
  340. ]]></content:encoded>
  341. <wfw:commentRss>https://akrabat.com/saving-the-current-url-to-a-note/feed/</wfw:commentRss>
  342. <slash:comments>0</slash:comments>
  343. </item>
  344. <item>
  345. <title>Accessing Longplay info for SwiftBar</title>
  346. <link>https://akrabat.com/accessing-longplay-info-for-swiftbar/</link>
  347. <comments>https://akrabat.com/accessing-longplay-info-for-swiftbar/#respond</comments>
  348. <dc:creator><![CDATA[Rob]]></dc:creator>
  349. <pubDate>Tue, 12 Aug 2025 10:00:00 +0000</pubDate>
  350. <category><![CDATA[Mac]]></category>
  351. <guid isPermaLink="false">https://akrabat.com/?p=7447</guid>
  352.  
  353. <description><![CDATA[One app that I find incredibly useful is SwiftBar and one use I have is to display track info for the currently playing song in Apple Music. SwiftBar plugins work as shell scripts that execute on a timer and echo specially formatted text which SwiftBar then turns into an item on the menu bar with an attached menu I use a heavily modified Now Playing plugin that was originally written by Adam Kenyon, so all… <a href="https://akrabat.com/accessing-longplay-info-for-swiftbar/">continue reading</a>.]]></description>
  354. <content:encoded><![CDATA[<p>One app that I find incredibly useful is <a href="https://github.com/swiftbar/SwiftBar">SwiftBar</a> and one use I have is to display track info for the currently playing song in Apple Music.</p>
  355. <p>SwiftBar plugins work as shell scripts that execute on a timer and echo specially formatted text which SwiftBar then turns into an item on the menu bar with an attached menu</p>
  356. <p>I use a heavily modified <a href="https://github.com/matryer/xbar-plugins/blob/main/Music/nowplaying.5s.sh">Now Playing plugin</a> that was originally written by Adam Kenyon, so all the hard work was done by them.</p>
  357. <p>Recently, I've been using <a href="https://longplay.rocks">Longplay</a> to play albums and wanted the same functionality.</p>
  358. <p>Now Playing uses AppleScript to determine if a music player is playing and what the track info is:</p>
  359. <pre>
  360. app_playing=$(osascript -e "tell application \"$i\" to player state as string")
  361. </pre>
  362. <p>And</p>
  363. <pre>
  364. track=$(osascript -e "tell application \"$app\" to name of current track")
  365. artist=$(osascript -e "tell application \"$app\" to artist of current track")
  366. </pre>
  367. <p>When looking at adding Longplay, I was pleased to discover that it has AppleScript support, but perusing the Dictionary, I discovered that it doesn't support the features I need here.</p>
  368. <p>Upon emailing the developer, they very helpfully pointed out that Longplay also has Shortcuts support and that I could probably use that instead. They were right.</p>
  369. <p>I knocked up a couple of shortcuts:</p>
  370. <picture><source  
  371. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-status-dark.png"
  372.        media="(prefers-color-scheme: dark)"
  373.    /><source
  374. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-status-light.png"
  375.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  376.    /><br />
  377.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-status-light.png" loading="lazy" alt="Longplay status Apple Shortcut" class="noborder" width="431"/>
  378. </picture>
  379. <p>and</p>
  380. <picture><source  
  381. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-now-playing-dark.png"
  382.        media="(prefers-color-scheme: dark)"
  383.    /><source
  384. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-now-playing-light.png"
  385.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  386.    /><br />
  387.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-now-playing-light.png" loading="lazy" alt="Longplay now playing light." class="noborder" width="431"/>
  388. </picture>
  389. <p>With these set-up, I can now run them from the command line using <tt>shortcuts</tt>:</p>
  390. <pre>
  391. app_playing=$(shortcuts run "Longplay status");
  392. </pre>
  393. <p>This will set <tt>$app_playing</tt> to either "Yes" or "No" as strings as it is defined as boolean in Shortcuts.</p>
  394. <pre>
  395. track=$(shortcuts run "Longplay now playing");
  396. </pre>
  397. <p>This simply sets <tt>$track</tt> to the string of the currently playing track.</p>
  398. <h2>Updated Now Playing script</h2>
  399. <p>With the ability to get the info I needed from the command line, I <s>hacked</s> updated my copy of the Now Playing script and all is good.</p>
  400. <p>I've updated it a bit over the years, so I've uploaded my version to Gist: <a href="https://gist.github.com/akrabat/8bcfac9dfef5fd4e9b67ac5bb504ea7a">nowplaying.5s.sh</a>.</p>
  401. ]]></content:encoded>
  402. <wfw:commentRss>https://akrabat.com/accessing-longplay-info-for-swiftbar/feed/</wfw:commentRss>
  403. <slash:comments>0</slash:comments>
  404. </item>
  405. <item>
  406. <title>Responding to StreamDeck buttons with Keyboard Maestro</title>
  407. <link>https://akrabat.com/controlling-the-streamdeck-via-keyboard-maestro/</link>
  408. <comments>https://akrabat.com/controlling-the-streamdeck-via-keyboard-maestro/#respond</comments>
  409. <dc:creator><![CDATA[Rob]]></dc:creator>
  410. <pubDate>Tue, 05 Aug 2025 10:00:00 +0000</pubDate>
  411. <category><![CDATA[AppleScript]]></category>
  412. <category><![CDATA[Keyboard Maestro]]></category>
  413. <category><![CDATA[Mac]]></category>
  414. <guid isPermaLink="false">https://akrabat.com/?p=7440</guid>
  415.  
  416. <description><![CDATA[I run Apple Music on my Mac desktop and send the output to my HomePod minis. To control the volume, you need to manipulate the Apple Music volume slider rather than the global volume controls for the Mac. It's easier to press buttons than use a mouse, so I used Keyboard Maestro to respond to two buttons on my Stream Deck instead. This is possible because Keyboard Maestro has a Stream Deck Plugin, so you… <a href="https://akrabat.com/controlling-the-streamdeck-via-keyboard-maestro/">continue reading</a>.]]></description>
  417. <content:encoded><![CDATA[<p>I run Apple Music on my Mac desktop and send the output to my HomePod minis. To control the volume, you need to manipulate the Apple Music volume slider rather than the global volume controls for the Mac.</p>
  418. <p>It's easier to press buttons than use a mouse, so I used <a href="https://www.keyboardmaestro.com/">Keyboard Maestro</a> to respond to two buttons on my <a href="https://www.elgato.com/uk/en/p/stream-deck">Stream Deck</a> instead.</p>
  419. <p>This is possible because Keyboard Maestro has a <a href="https://marketplace.elgato.com/product/keyboard-maestro-35c7590b-b7fb-4be0-9e5d-9fd4b4c0f013">Stream Deck Plugin</a>, so you need to install that first.</p>
  420. <h2>Setting up the Stream Deck button</h2>
  421. <p>You can now assign Keyboard Maestro to a button in the Stream Deck software:</p>
  422. <p><img fetchpriority="high" decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025streamdeck-km-button.png" alt="Keyboard Maestro automation configuration interface showing a button setup with a speaker/volume icon. The interface displays fields for Title (empty), Button ID (R3C1), Virtual Row (3), and Virtual Column (1). The left side shows a black square button with white speaker and minus icons." title="streamdeck-km-button.png" border="0" width="500" height="239" /></p>
  423. <p>This is the configuration for my volume down button, as you can tell by the icon I chose. The Button ID defaults to the row and column number of where you have placed it on the Stream Deck.</p>
  424. <h2>Responding to the button in Keyboard Maestro</h2>
  425. <p>On the Keyboard Maestro side, we need a macro that is trigged by the Stream Deck button. This is easy to do as it looks like a USB device key and you can press the button the Stream Deck and Keyboard Maestro will recognise it and fill in the correct details.</p>
  426. <p><img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025streamdeck-km-control.png" alt="Screenshot of an Keyboard Maestro automation interface showing a "Music volume down" macro. The trigger is a Stream Deck R3C1 button press with modifiers. The action executes AppleScript code that decreases the Music app's volume by 1, with a minimum volume of 0." title="streamdeck-km-control.png" border="0" width="500" height="529" /></p>
  427. <p>Upon clicking the button, we simply run some AppleScript to control the Music app's volume.</p>
  428. <h2>That's it</h2>
  429. <p>That's all there is to responding to a button on the Stream Deck on a Mac. In this case, I'm using AppleScript, but Keyboard Maestro lets you do practically anything on the computer!</p>
  430. ]]></content:encoded>
  431. <wfw:commentRss>https://akrabat.com/controlling-the-streamdeck-via-keyboard-maestro/feed/</wfw:commentRss>
  432. <slash:comments>0</slash:comments>
  433. </item>
  434. <item>
  435. <title>Step-debugging Docker Compose NestJS services</title>
  436. <link>https://akrabat.com/step-debugging-docker-compose-nestjs-services/</link>
  437. <comments>https://akrabat.com/step-debugging-docker-compose-nestjs-services/#respond</comments>
  438. <dc:creator><![CDATA[Rob]]></dc:creator>
  439. <pubDate>Tue, 29 Jul 2025 10:00:00 +0000</pubDate>
  440. <category><![CDATA[NodeJS]]></category>
  441. <category><![CDATA[TypeScript]]></category>
  442. <guid isPermaLink="false">https://akrabat.com/?p=7454</guid>
  443.  
  444. <description><![CDATA[I'm working on a NestJS project that uses monorepo mode. It consists of a number of separate microservice applications that each have their own Docker container that are managed in development using Docker Compose. I like step-debugging in my IDE and so needed to set it up for this application. This is what I did. The application setup Each service in our project has its own container in docker-compose.yaml and we use a reverse proxy to… <a href="https://akrabat.com/step-debugging-docker-compose-nestjs-services/">continue reading</a>.]]></description>
  445. <content:encoded><![CDATA[<p>I'm working on a <a href="https://nestjs.com">NestJS</a> project that uses <a href="https://docs.nestjs.com/cli/monorepo#monorepo-mode">monorepo mode</a>. It consists of a number of separate microservice applications that each have their own <a href="https://www.docker.com">Docker</a> container that are managed in development using <a href="https://docs.docker.com/compose/">Docker Compose</a>.</p>
  446. <p>I like step-debugging in my IDE and so needed to set it up for this application. This is what I did.</p>
  447. <h2>The application setup</h2>
  448. <p>Each service in our project has its own container in docker-compose.yaml and we use a reverse proxy to have a single endpoint that routes requests to the correct microservice. Each custom <tt>Dockerfile</tt> runs <tt>ppm run start:dev:{service name}></tt> which is defined in <tt>package.json</tt> like this:</p>
  449. <pre>
  450. "start:dev:service1": "nest start service1 --watch --tsc --watchOptions.poll=1000 --preserveWatchOutput",
  451. </pre>
  452. <p>Using <tt>--watchOptions.poll=1000</tt> is just more reliable when running in Docker with volumes mounted into the container. We also set the <tt>--preserveWatchOutput</tt> flag to ensure that the service doesn't take control of the terminal as this is unhelpful when you have multiple services in play.</p>
  453. <h2>Set the apps up for debugging</h2>
  454. <p>We need to make some modification for step debugging. Firstly, I created a set of <tt>start:debug:{service name}</tt> scripts in <tt>package.json</tt> that look like this:</p>
  455. <pre>
  456. "start:debug:service1": "nest start service1 --debug 0.0.0.0:9229 --watch --tsc --watchOptions.poll=1000 --preserveWatchOutput",
  457. </pre>
  458. <p>We enabled the <tt>--debug</tt> flag to enable node's <tt>--inspect</tt> flag so that port <tt>9229</tt> is available to the debugger. However, buy default this is bound to <tt>127.0.0.1</tt> which is not useful in a container, so we bind to <tt>0.0.0.0:9229</tt> so that it's available outside the container.</p>
  459. <p>Next, we need to our new <tt>start:debug:{service name}</tt> scripts and expose port 92229 to our local environment for each service. We do this in <tt>docker-compose.override.yaml</tt>:</p>
  460. <pre lang="yaml">
  461. services:
  462.  service1:
  463.    ports:
  464.      - "9230:9229"
  465.    command: pnpm run start:debug:service1
  466.  
  467.  service2:
  468.    ports:
  469.      - "9231:9229"
  470.    command: pnpm run start:debug:service2
  471. </pre>
  472. <p>I don't tend to like binding to the default port as that invariably confuses me when I run some test thing locally, so I've picked ports starting from <tt>9230</tt> onwards for my services.</p>
  473. <p>Running <tt>docker compose up</tt> will now start the containers.</p>
  474. <h2>Debugging in WebStorm</h2>
  475. <p>To set up <a href="https://www.jetbrains.com/webstorm/">WebStorm</a> for step debugging, create a <em>Run/Debug configuration</em> entry of type <tt>Attach to Node.js/Chrome</tt> for each container.</p>
  476. <p>The settings for service1 are:</p>
  477. <ul>
  478. <li>Name: Debug service1</li>
  479. <li>Host: localhost</li>
  480. <li>Port: 9230</li>
  481. </ul>
  482. <p>For the other services, change the name and port.</p>
  483. <picture><source  
  484. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025webstorm-nodejs-debug-config-dark.png"
  485.        media="(prefers-color-scheme: dark)"
  486.    /><source
  487. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025webstorm-nodejs-debug-config-light.png"
  488.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  489.    /><br />
  490.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025webstorm-nodejs-debug-config-light.png" loading="lazy" alt="Webstorm nodejs debug config light." class="noborder" width="600"/>
  491. </picture>
  492. <p>From the Debugging dropdown at in the title bar select the service and press the green "bug" button. You'll see a "Debugger attached." message in the Docker logs.</p>
  493. <p>Attach a breakpoint and access the endpoint using <tt>curl</tt> or another HTTP client and the IDE should stop execution at the breakpoint.</p>
  494. <h2>Debugging in VS Code</h2>
  495. <p>To set up <a href="https://code.visualstudio.com">VS Code</a> for step debugging, create a <tt>.vscode/launch.json</tt> file in your project. It should look like this:</p>
  496. <pre lang="json">
  497. {
  498.  "version": "0.2.0",
  499.  "configurations": [
  500.    {
  501.      "name": "Debug service1",
  502.      "type": "node",
  503.      "request": "attach",
  504.      "port": 9230,
  505.      "address": "localhost",
  506.      "localRoot": "${workspaceFolder}",
  507.      "remoteRoot": "/usr/src/app",
  508.      "restart": true
  509.    },
  510.    {
  511.      "name": "Debug service2",
  512.      "type": "node",
  513.      "request": "attach",
  514.      "port": 9231,
  515.      "address": "localhost",
  516.      "localRoot": "${workspaceFolder}",
  517.      "remoteRoot": "/usr/src/app",
  518.      "restart": true
  519.    }
  520.  ]
  521. }
  522. </pre>
  523. <p>Set <tt>remoteRoot</tt> to the directory within the Docker container where the project is mounted.</p>
  524. <p>Start debugging by selecting the <em>Run and Debug</em> pane in the left hand toolbar and choose the service from the dropdown at the top. Then press the green <em>Start debugging</em> button (or press F5).You'll see a "Debugger attached." message in the Docker logs.</p>
  525. <p>Attach a breakpoint and access the endpoint using <tt>curl</tt> or another HTTP client and the IDE should stop execution at the breakpoint.</p>
  526. <h2>That's it</h2>
  527. <p>Having done this, I can now enjoy step debugging this new-to-me codebase and understand what it does!</p>
  528. ]]></content:encoded>
  529. <wfw:commentRss>https://akrabat.com/step-debugging-docker-compose-nestjs-services/feed/</wfw:commentRss>
  530. <slash:comments>0</slash:comments>
  531. </item>
  532. <item>
  533. <title>QuickSS: Screenshot the active window on Mac</title>
  534. <link>https://akrabat.com/quickss-screenshot-the-active-window-on-mac/</link>
  535. <comments>https://akrabat.com/quickss-screenshot-the-active-window-on-mac/#respond</comments>
  536. <dc:creator><![CDATA[Rob]]></dc:creator>
  537. <pubDate>Tue, 22 Jul 2025 09:00:00 +0000</pubDate>
  538. <category><![CDATA[Mac]]></category>
  539. <category><![CDATA[Software]]></category>
  540. <guid isPermaLink="false">https://akrabat.com/?p=7418</guid>
  541.  
  542. <description><![CDATA[Back in 2016, I wrote about using QuickGrab to take a screenshot of the active window via a single key press with no mouse use required. It's now 2025 and I'm still using this and Apple has announced that Rosetta 2 will be phased out in a couple of years. As QuickGrab is one of the few Intel-only apps I still use, I thought I'd recompile for Apple Silicon and it was then that I… <a href="https://akrabat.com/quickss-screenshot-the-active-window-on-mac/">continue reading</a>.]]></description>
  543. <content:encoded><![CDATA[<p>Back in 2016, I wrote about using QuickGrab to take a screenshot of the active window via a single key press with no mouse use required.</p>
  544. <p>It's now 2025 and I'm still using this and Apple has announced that Rosetta 2 will be phased out in a couple of years. As QuickGrab is one of the few Intel-only apps I still use, I thought I'd recompile for Apple Silicon and it was then that I ran into trouble:</p>
  545. <pre>
  546. $ gcc -framework cocoa -x objective-c -o quickgrab quickgrab.m
  547. quickgrab.m:92:26: error: 'CGWindowListCreateImage' is unavailable:
  548. obsoleted in macOS 15.0 - Please use ScreenCaptureKit instead.
  549. </pre>
  550. <p><em>Obsoleted in macOS 15.0</em> isn't something you want to see! The APIs that QuickGrab use are no longer part of the SDK and so it cannot be compiled. Hence I decided to replace it with a Swift version and also take the opportunity to add additional features that I wanted.</p>
  551. <p>While on holiday, I wrote <a href="https://github.com/akrabat/QuickSS">QuickSS</a>. I spent some time playing around with ScreenCaptureKit, but couldn't get it to replicate the window shadows that the standard screenshot tool on <tt>shift+cmd+4</tt> does. To solve this I decided to use <tt>screencapture</tt> to take the screenshot which is provided by macOS. </p>
  552. <h2>Screenshot to file</h2>
  553. <p>To save a screenshot of the active window directly to a file, just run <tt>quickss</tt>. You probably want to select cmd+tab to a different window, so prefix with <tt>sleep</tt>:</p>
  554. <pre>
  555. rob@Caledonia QuickGrab (master *) $ sleep 3; quickss
  556. Capturing window ID: 5940 [Safari: 'Rob Allen – Pragmatism in the real world']
  557. Screenshot saved to: /Users/rob/Downloads/Screenshot 2025-07-22 at 11.00.00.png
  558. </pre>
  559. <p>As it's my app, I made some changes to match the way I work with it. Firstly it defaults to saving the file to the Downloads folder and names it the same as the default screenshot utility. You can use <tt>--file</tt> to override this should you need to and <tt>--quiet</tt> will output just the filename which is useful for onward scripting, or displaying in notifications.</p>
  560. <h2>Screenshot directly to clipboard</h2>
  561. <p>I also added the ability to put the screenshot directly onto the clipboard with <tt>--clipboard</tt></p>
  562. <pre>
  563. $ sleep 3; quickss --clipboard
  564. Capturing window ID: 5940 [Safari: 'Rob Allen – Pragmatism in the real world']
  565. Screenshot copied to clipboard
  566. </pre>
  567. <p>I can now paste directly into Slack/Discord/Messages/Messenger/WhatsApp/Signal/etc. (Yes, there are far too many of these services nowadays!)</p>
  568. <h2>Keyboard shortcut</h2>
  569. <p>Obviously, the best way to run this is to use global keyboard shortcuts. My preference is to use <tt style="font-family: sans-serif">⌃⌥⌘4</tt> to put the screenshot onto the clipboard and <tt style="font-family: sans-serif">⇧⌃⌥⌘4</tt> to save it to file.</p>
  570. <h3>Using Shortcuts.app</h3>
  571. <p>You can use the Shortcuts app for this and create a shortcut for copying to clipboard like this:</p>
  572. <p><img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025quickss-take-screenshot-to-cliboard.png" alt="Quickss take screenshot to cliboard." title="quickss-take-screenshot-to-cliboard.png" border="0" width="700" height="511" /></p>
  573. <p>The equivalent for saving to file is the same, except that you don't need the <tt>--clipboard</tt> parameter.</p>
  574. <h3>Using Alfred</h3>
  575. <p>I use <a href="https://www.alfredapp.com/">Alfred</a> for these sort of things, so I wrote a Workflow to do this based on the previous one:</p>
  576. <p><img loading="lazy" decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025quickss-alfred-workflow.png" alt="Quickss alfred workflow." title="quickss-alfred-workflow.png" border="0" width="700" height="363" /></p>
  577. <p>It's downloadable from the <a href="https://github.com/akrabat/QuickSS/releases/latest">QuickSS latest release</a> page.</p>
  578. <h2>That's it</h2>
  579. <p>Over the years, I've found that having a global hotkey to screenshot the current active window is really helpful. I like it even more now that it goes directly to the clipboard.</p>
  580. ]]></content:encoded>
  581. <wfw:commentRss>https://akrabat.com/quickss-screenshot-the-active-window-on-mac/feed/</wfw:commentRss>
  582. <slash:comments>0</slash:comments>
  583. </item>
  584. <item>
  585. <title>Notarising a macOS standalone binary</title>
  586. <link>https://akrabat.com/notarising-a-macos-standalone-binary/</link>
  587. <comments>https://akrabat.com/notarising-a-macos-standalone-binary/#respond</comments>
  588. <dc:creator><![CDATA[Rob]]></dc:creator>
  589. <pubDate>Tue, 15 Jul 2025 09:00:00 +0000</pubDate>
  590. <category><![CDATA[Command Line]]></category>
  591. <category><![CDATA[Development]]></category>
  592. <category><![CDATA[Shell Scripting]]></category>
  593. <guid isPermaLink="false">https://akrabat.com/?p=7413</guid>
  594.  
  595. <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>
  596. <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>
  597. <pre>
  598. swiftc quickss.swift -o quickss
  599. </pre>
  600. <p>To distribute it on modern Macs, I need to sign it and then get Apple to notarise it.</p>
  601. <h2>Signing the binary</h2>
  602. <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>
  603. <p>Download your Developer ID Application certificate and add to Keychain Access.</p>
  604. <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>
  605. <pre>
  606. IDENTITY=$(security find-identity -v -p codesigning | grep "Developer ID Application" | grep -m 1 -oE '"[^"]+"' | tr -d '"')
  607. </pre>
  608. <p>To actually sign the binary, use the <tt>codesign</tt> utility:</p>
  609. <pre>
  610. codesign --timestamp --options runtime --sign "$IDENTITY" quickss
  611. </pre>
  612. <h2>Notarising</h2>
  613. <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>
  614. <p>Submit your app for notarising with:</p>
  615. <pre>
  616. zip -q quickss.zip quickss
  617.  
  618. xcrun notarytool submit  --wait --no-progress -f json \
  619.    --team-id "$TEAM_ID" \
  620.    --apple-id "$APPLE_ID" \
  621.    --password "$APP_SPECIFIC_PASSWORD" \
  622.    quickss.zip
  623.  
  624. rm quickss.zip
  625. </pre>
  626. <p>If it fails, use the submission ID to query the log:</p>
  627. <pre>
  628. xcrun notarytool log \
  629.    --team-id "$TEAM_ID" \
  630.    --apple-id "$APPLE_ID" \
  631.    --password "$APP_SPECIFIC_PASSWORD" \
  632.    "&lt;submission ID&gt;"
  633. </pre>
  634. <p>Apple has now notarised your app.</p>
  635. <h2>Note: No need to staple</h2>
  636. <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>
  637. <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>
  638. <p>For standalone binaries GateKeeper checks directly with Apple's servers the first time they are run, so stapling is unnecessary.</p>
  639. <h2>That's it</h2>
  640. <p>That's it. You now have a notarised standalone binary that can be easily distributed.</p>
  641. ]]></content:encoded>
  642. <wfw:commentRss>https://akrabat.com/notarising-a-macos-standalone-binary/feed/</wfw:commentRss>
  643. <slash:comments>0</slash:comments>
  644. </item>
  645. <item>
  646. <title>Using the 1Password CLI in a script</title>
  647. <link>https://akrabat.com/using-the-1password-cli-in-a-script/</link>
  648. <comments>https://akrabat.com/using-the-1password-cli-in-a-script/#respond</comments>
  649. <dc:creator><![CDATA[Rob]]></dc:creator>
  650. <pubDate>Tue, 08 Jul 2025 09:00:00 +0000</pubDate>
  651. <category><![CDATA[Command Line]]></category>
  652. <category><![CDATA[Development]]></category>
  653. <category><![CDATA[Shell Scripting]]></category>
  654. <guid isPermaLink="false">https://akrabat.com/?p=7407</guid>
  655.  
  656. <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>
  657. <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>
  658. <p>The 1Password CLI tool is call <tt>op</tt>. I installed it via Homebrew with:</p>
  659. <pre>brew install 1password-cli</pre>
  660. <h2>Sign in</h2>
  661. <p>You need to sign in. </p>
  662. <pre>op signin</pre>
  663. <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>
  664. <pre>export OP_ACCOUNT=my.1password.com</pre>
  665. <p>Alternatively, use the <tt>--account</tt> parameter.</p>
  666. <p>You get a dialog box where for me, I use TouchID to sign in.</p>
  667. <p><tt>op signin</tt> is idempotent so is a no-op if the Terminal is already signed in.</p>
  668. <h2>Access data</h2>
  669. <p>There are multiple ways to retrieve the data from a 1Password item.</p>
  670. <h3><tt style="color:inherit">op item get</tt></h3>
  671. <p>Use <tt>op item get "&lt;item&gt;" --field "&lt;fieldname&gt;"</tt> to get a specific field. e.g</p>
  672. <pre>op item get "Apple App Notarisation" --field "username"</pre>
  673. <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>
  674. <p>Note, that for a password, you also need to pass in <tt>--reveal</tt>.</p>
  675. <p>As I'm writing a script, I assign to a variable:</p>
  676. <pre>
  677. APPLE_ID=$(op item get "Apple App Notarisation" --field username)
  678. APP_SPECIFIC_PASSWORD=$(op item get "Apple App Notarisation" --field password --reveal)
  679. TEAM_ID=$(op item get "Apple App Notarisation" --field team_id)
  680. </pre>
  681. <p>Alternatively, you can get back multiple fields in one go by providing a list of comma separated fields:</p>
  682. <pre>
  683. FIELDS=$(op item get "Apple App Notarisation" --fields username,password,team_id --reveal)
  684. IFS=',' read -r APPLE_ID APP_SPECIFIC_PASSWORD TEAM_ID <<< "$FIELDS"
  685. </pre>
  686. <h3><tt style="color:inherit">op read</tt></h3>
  687. <p>You can also use the <tt>read parameter</tt> which takes a URL-style path:</p>
  688. <pre>
  689. op read op://&lt;vault&gt;/&lt;item&gt;/&lt;field&gt;
  690. </pre>
  691. <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>
  692. <p>For my case, I can use:</p>
  693. <pre>
  694. APPLE_ID=$(read "op://Private/Apple App Notarisation/username")
  695. APP_SPECIFIC_PASSWORD=$(op read "op://Private/Apple App Notarisation/password")
  696. TEAM_ID=$(op read "op://Private/Apple App Notarisation/team_id")
  697. </pre>
  698. <h3>Format as JSON</h3>
  699. <p>You can also get the entire item in JSON using:</p>
  700. <pre>op item get "Apple App Notarisation" --format json</pre>
  701. <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>
  702. <pre>op item get "Apple App Notarisation" --format json | jq -r '
  703.  .fields[] | select(.label=="username" or .label=="password") | "\(.label): \(.value)"
  704. '</pre>
  705. <h3>That's it</h3>
  706. <p>That's it. Very simple to put into a script and keep my password secure.</p>
  707. ]]></content:encoded>
  708. <wfw:commentRss>https://akrabat.com/using-the-1password-cli-in-a-script/feed/</wfw:commentRss>
  709. <slash:comments>0</slash:comments>
  710. </item>
  711. </channel>
  712. </rss>
  713.  

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