<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Figueirego Blog – Automation</title><link>https://figueirego.com/blog/tags/automation/</link><description>Recent posts in Automation on Figueirego Blog.</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 19 Oct 2026 04:33:15 -0300</lastBuildDate><atom:link href="https://figueirego.com/blog/tags/automation/index.xml" rel="self" type="application/rss+xml"/><item><title>Building my blog, part 4: uploading images to S3 from Finder</title><link>https://figueirego.com/blog/posts/building-the-blog-04-images/</link><pubDate>Mon, 19 Oct 2026 04:15:50 -0300</pubDate><guid>https://figueirego.com/blog/posts/building-the-blog-04-images/</guid><description>&lt;p&gt;&lt;img src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-images/imagens.webp" alt="Photographs arranged on a desk, archive drawers and a computer showing image previews."&gt;&lt;/p&gt;&lt;p&gt;Select an image, right-click, and receive a URL ready for the article. That was the convenience I wanted in my blogging routine. Automating AWS took the most work after the design.&lt;/p&gt;
&lt;p&gt;The path began with an upload helper and became a Finder Quick Action. The successful case was easy to describe. The work appeared in the next questions: what happens when the session expires, when I abandon login, or when the upload fails?&lt;/p&gt;
&lt;h2 data-hextra-search-id="pages-on-hostinger-images-on-s3"&gt;Pages on Hostinger, images on S3&lt;span class="hx:absolute hx:-mt-20" id="pages-on-hostinger-images-on-s3"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;S3 stores objects: in this case, image files. Pages remain on Hostinger. Markdown contains the file&amp;rsquo;s public URL, and the browser retrieves the image from that address.&lt;/p&gt;
&lt;p&gt;This separates image uploads from the website build. Adding a file to the repository does not execute an AWS upload. Some local assets also travel with the build; new editorial images use the S3 workflow.&lt;/p&gt;
&lt;p&gt;A published image URL needs to work after my session ends. That is why the article does not use a temporary signed URL. The image prefix has public read access configured, while writing requires authentication. The upload script does not make any arbitrary private bucket public.&lt;/p&gt;
&lt;h2 data-hextra-search-id="what-i-mean-by-image-versions"&gt;What I mean by image versions&lt;span class="hx:absolute hx:-mt-20" id="what-i-mean-by-image-versions"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Each upload receives a new name containing the year, month, a normalized name, and an identifier. An illustrative path would be:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;images/2026/10/my-cover-unique-identifier.webp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If I correct a cover, I upload another file and update the article&amp;rsquo;s reference. The earlier image remains available until it is removed. That helps prevent a URL with long-lived caching from unexpectedly representing different content.&lt;/p&gt;
&lt;p&gt;This differs from &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.html" target="_blank" rel="noopener"&gt;S3 Versioning&lt;/a&gt;, which retains versions of the same object within the bucket. Here I am describing new keys and new URLs. Having two files does not establish that native versioning is enabled.&lt;/p&gt;
&lt;p&gt;Git history records changes to the reference in an article. By itself, it is not a backup of external files. Preserving and recovering images also requires looking after their storage.&lt;/p&gt;
&lt;h2 data-hextra-search-id="a-dedicated-upload-identity"&gt;A dedicated upload identity&lt;span class="hx:absolute hx:-mt-20" id="a-dedicated-upload-identity"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The shortcut uses an AWS CLI profile with temporary credentials for a dedicated user. It does not depend on a root session or store a permanent access key inside the project.&lt;/p&gt;
&lt;p&gt;The account and permissions are prepared beforehand. The helper uses that existing destination; it does not provision an entire AWS environment. The &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sign-in.html" target="_blank" rel="noopener"&gt;AWS CLI&amp;rsquo;s local browser login&lt;/a&gt; renews authentication without putting a password or code in an article, script, or conversation.&lt;/p&gt;
&lt;p&gt;In the configured project, installation of the Quick Action starts from the repository root:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 blog/scripts/install-finder-upload.py&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
aria-label="Copy code"
data-copied-label="Copied"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This command belongs to the private repository and requires the environment described above. It is not a universal public installer for any AWS account. Reproducing the idea requires adapting the destination, profile, and permissions to your environment.&lt;/p&gt;
&lt;h2 data-hextra-search-id="the-finder-routine"&gt;The Finder routine&lt;span class="hx:absolute hx:-mt-20" id="the-finder-routine"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;With the action installed, I select the image and choose &lt;strong&gt;Quick Actions → Enviar imagem ao S3 — Figueirego&lt;/strong&gt;. After uploading, the action copies the URL and shows a completion notification. I do not need to open Terminal for the normal routine.&lt;/p&gt;
&lt;p&gt;If the session expired, the shortcut opens browser authentication and waits for completion. Once login is authorized, it continues uploading. Testing the full path through Finder was important: running the helper in Terminal alone would not demonstrate that notifications appeared through the macOS action.&lt;/p&gt;
&lt;p&gt;We also checked the public URL and the received image. A completion message helps, but it does not replace checking that the correct file is accessible outside the AWS session.&lt;/p&gt;
&lt;figure class="reader-figure reader-figure-wide"&gt;
&lt;div class="post-cover cover-reader"&gt;
&lt;img class="cover-image" src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-images/finder-acao-real.webp" alt="Finder Quick Actions menu containing Enviar imagem ao S3 — Figueirego." width="1840" height="872" loading="lazy" decoding="async"&gt;&lt;/div&gt;&lt;figcaption&gt;The installed Finder action is available by right-clicking an image. The menu was opened for this screenshot without performing another upload; the custom action keeps its Portuguese name.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-images/finder-acao-real.webp" target="_blank" rel="noopener"&gt;View the full-size screenshot&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-hextra-search-id="cancellation-is-part-of-the-interface"&gt;Cancellation is part of the interface&lt;span class="hx:absolute hx:-mt-20" id="cancellation-is-part-of-the-interface"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Waiting five minutes to discover that a login had been abandoned was a poor experience. The waiting step gained a window with &lt;strong&gt;Cancelar envio&lt;/strong&gt; (Cancel upload), making it possible to explicitly end that attempt.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Expected behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Valid session&lt;/td&gt;
&lt;td&gt;Upload, copy the URL, and announce completion.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expired session&lt;/td&gt;
&lt;td&gt;Open login and continue after authentication.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cancellation while waiting for login&lt;/td&gt;
&lt;td&gt;Notify, do not upload the image, and preserve clipboard contents.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Another attempt after cancellation&lt;/td&gt;
&lt;td&gt;Allow the workflow to start again.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Closing the browser tab alone is not equivalent to pressing the cancellation button. And that button cancels the authentication wait; it does not promise to interrupt an upload that has already started.&lt;/p&gt;
&lt;p&gt;We tested the waiting step first with simulated AWS behavior and then with real login. The checks answer different questions: simulation helps control the scenario; the real test confirms the integration that will be used.&lt;/p&gt;
&lt;figure class="reader-figure reader-figure-wide"&gt;
&lt;div class="post-cover cover-reader"&gt;
&lt;img class="cover-image" src="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-images/finder-en.webp" alt="From an image in Finder to a copied URL. Cancelling sign-in stops the flow before uploading." width="1200" height="1000" loading="lazy" decoding="async"&gt;&lt;/div&gt;&lt;figcaption&gt;From an image in Finder to a copied URL. Cancelling sign-in stops the flow before uploading.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href="https://figueirego-blog-images.s3.us-east-1.amazonaws.com/images/2026/10/building-blog-images/finder-en.webp" target="_blank" rel="noopener"&gt;Open the full-size diagram&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-hextra-search-id="what-still-requires-care"&gt;What still requires care&lt;span class="hx:absolute hx:-mt-20" id="what-still-requires-care"&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I do not automatically remove an old image just because I uploaded another one. A draft, another article, or a feed already received by a reader may still refer to it. Cleanup needs to account for those references.&lt;/p&gt;
&lt;p&gt;If the upload succeeds but copying to the clipboard fails, that does not undo the uploaded object either. Distinguishing an upload failure from failure in the final convenience step avoids unnecessary repeat uploads.&lt;/p&gt;
&lt;p&gt;Automation shortened the routine. The lesson was to give cancellation and recovery proper attention too: a tool that is simple to use should explain when it finished, when it failed, and what happened to the file.&lt;/p&gt;
&lt;nav id="series-navigation" class="series-navigation" aria-label="Building my blog"&gt;
&lt;div class="series-heading"&gt;
&lt;h2&gt;Building my blog&lt;/h2&gt;
&lt;p&gt;Explore all 5 parts of the series.&lt;/p&gt;
&lt;/div&gt;
&lt;ol class="series-chapters" role="list"&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-01-architecture/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;01&lt;/span&gt;
&lt;span class="series-title"&gt;Idea and architecture&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-02-reading/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;02&lt;/span&gt;
&lt;span class="series-title"&gt;Design and reading&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-03-publishing/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;03&lt;/span&gt;
&lt;span class="series-title"&gt;Publishing&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span class="series-chapter" aria-current="page"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;04&lt;/span&gt;
&lt;span class="series-title"&gt;Images and automation&lt;/span&gt;
&lt;span class="series-current"&gt;You are here&lt;/span&gt;
&lt;/span&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a class="series-chapter" href="https://figueirego.com/blog/posts/building-the-blog-05-seo-rss/"&gt;
&lt;span class="series-number" aria-hidden="true"&gt;05&lt;/span&gt;
&lt;span class="series-title"&gt;SEO and RSS&lt;/span&gt;
&lt;svg width="20" height="20" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"&gt;&lt;path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/&gt;&lt;/svg&gt;
&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/nav&gt;</description></item></channel></rss>