<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://klevo.sk/feed.xml" rel="self" type="application/atom+xml" /><link href="https://klevo.sk/" rel="alternate" type="text/html" /><updated>2026-04-30T16:27:20+00:00</updated><id>https://klevo.sk/feed.xml</id><title type="html">Klevo</title><subtitle>Full stack developer building (web) applications, infrastructure and interfaces.</subtitle><entry><title type="html">openai/openai-ruby: Ruby 4 compatibility</title><link href="https://klevo.sk/open-source/openai-openai-ruby-ruby-4-compatibility/" rel="alternate" type="text/html" title="openai/openai-ruby: Ruby 4 compatibility" /><published>2026-01-10T09:16:57+00:00</published><updated>2026-01-10T09:16:57+00:00</updated><id>https://klevo.sk/open-source/openai-openai-ruby-ruby-4-compatibility</id><content type="html" xml:base="https://klevo.sk/open-source/openai-openai-ruby-ruby-4-compatibility/"><![CDATA[<p>A small <a href="https://github.com/openai/openai-ruby/pull/236">contribution to OpenAI’s Ruby API client</a> from yours truly was just merged.</p>

<p>It adds the required <code class="language-plaintext highlighter-rouge">cgi</code> gem dependency, which is no longer part of the stdlib in Ruby 4. It also expands CI to cover all supported Ruby versions to help prevent future issues like this.</p>

<!--more-->

<p><img src="/uploads/2026/01/ruby-in-the-forest.webp" alt="OpenAI Ruby client contribution" /></p>]]></content><author><name>Robert Starsi</name></author><category term="open-source" /><category term="ruby-and-rails" /><summary type="html"><![CDATA[A small contribution to OpenAI’s Ruby API client from yours truly was just merged. It adds the required cgi gem dependency, which is no longer part of the stdlib in Ruby 4. It also expands CI to cover all supported Ruby versions to help prevent future issues like this.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://klevo.sk/uploads/2026/01/ruby-in-the-forest.webp" /><media:content medium="image" url="https://klevo.sk/uploads/2026/01/ruby-in-the-forest.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Stepped Actions featured in Ruby Weekly #782</title><link href="https://klevo.sk/open-source/stepped-actions-featured-in-ruby-weekly-782/" rel="alternate" type="text/html" title="Stepped Actions featured in Ruby Weekly #782" /><published>2026-01-08T07:00:00+00:00</published><updated>2026-01-08T07:00:00+00:00</updated><id>https://klevo.sk/open-source/stepped-actions-featured-in-ruby-weekly-782</id><content type="html" xml:base="https://klevo.sk/open-source/stepped-actions-featured-in-ruby-weekly-782/"><![CDATA[<p>Stepped Actions was featured in <a href="https://rubyweekly.com/issues/782">Ruby Weekly #782</a>. Thanks for the mention and for helping more Rubyists find the project.</p>

<!--more-->

<p>If you missed the <a href="/projects/stepped-actions-rails-workflow-orchestration/">original release</a>, Stepped Actions is a Rails engine for orchestrating complex workflows as a tree of persisted actions that run through Active Job. It is built for long running processes, concurrency control and resuming work without losing state.</p>

<p>Check the docs and code at <a href="https://github.com/envirobly/stepped">GitHub: envirobly/stepped</a>.</p>

<p><img src="/uploads/2026/01/ruby-weekly-782-stepped-actions.webp" alt="Stepped Actions in Ruby Weekly #782" /></p>]]></content><author><name>Robert Starsi</name></author><category term="open-source" /><category term="ruby-and-rails" /><summary type="html"><![CDATA[Stepped Actions was featured in Ruby Weekly #782. Thanks for the mention and for helping more Rubyists find the project.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://klevo.sk/uploads/2025/12/stepped-actions-square.webp" /><media:content medium="image" url="https://klevo.sk/uploads/2025/12/stepped-actions-square.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Stepped Actions: workflow orchestration for Rails</title><link href="https://klevo.sk/projects/stepped-actions-rails-workflow-orchestration/" rel="alternate" type="text/html" title="Stepped Actions: workflow orchestration for Rails" /><published>2025-12-13T15:00:00+00:00</published><updated>2025-12-13T15:00:00+00:00</updated><id>https://klevo.sk/projects/stepped-actions-rails-workflow-orchestration</id><content type="html" xml:base="https://klevo.sk/projects/stepped-actions-rails-workflow-orchestration/"><![CDATA[<p>I just released <a href="https://github.com/envirobly/stepped">Stepped Actions</a>, a Rails engine for orchestrating complex workflows as a tree of persisted actions that run through Active Job. It’s extracted out of <a href="/projects/envirobly-efficient-application-hosting-platform/">Envirobly</a> and battle tested in it’s current form for over a year.</p>

<p><img src="/uploads/2025/12/stepped-actions-16-9.webp" alt="" /></p>

<!--more-->

<p>Stepped is built around a few ideas that I have wanted in Rails for a long time:</p>

<ul>
  <li><strong>Action trees</strong>: a root action runs step-by-step and each step can fan out into more actions.</li>
  <li><strong>Concurrency lanes</strong>: actions share a <code class="language-plaintext highlighter-rouge">concurrency_key</code> so work runs one-at-a-time where it should.</li>
  <li><strong>Reuse</strong>: an optional <code class="language-plaintext highlighter-rouge">checksum</code> can skip work that is already achieved or share currently-performing work.</li>
  <li><strong>Outbound completion</strong>: keep an action <code class="language-plaintext highlighter-rouge">performing</code> and complete it later from an external event.</li>
</ul>

<p>If you are on Rails <code class="language-plaintext highlighter-rouge">&gt;= 8.1.1</code>, the install is straightforward:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">gem</span> <span class="s2">"stepped"</span>
</code></pre></div></div>

<h3 id="links">Links</h3>

<ul>
  <li><a href="https://github.com/envirobly/stepped">Code and docs @GitHub</a></li>
  <li><a href="https://news.ycombinator.com/item?id=46261884">Hacker News post with discussion</a></li>
  <li><a href="https://rubyweekly.com/issues/782">Featured in Ruby Weekly #782</a></li>
</ul>]]></content><author><name>Robert Starsi</name></author><category term="open-source" /><category term="projects" /><category term="ruby-and-rails" /><category term="open-source" /><category term="rails" /><category term="active-job" /><summary type="html"><![CDATA[I just released Stepped Actions, a Rails engine for orchestrating complex workflows as a tree of persisted actions that run through Active Job. It’s extracted out of Envirobly and battle tested in it’s current form for over a year.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://klevo.sk/uploads/2025/12/stepped-actions-square.webp" /><media:content medium="image" url="https://klevo.sk/uploads/2025/12/stepped-actions-square.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Development Story of Envirobly and the Unexpected Pivot</title><link href="https://klevo.sk/projects/development-story-of-envirobly-and-the-unexpected-pivot/" rel="alternate" type="text/html" title="Development Story of Envirobly and the Unexpected Pivot" /><published>2025-12-08T19:17:00+00:00</published><updated>2025-12-08T19:17:00+00:00</updated><id>https://klevo.sk/projects/development-story-of-envirobly-and-the-unexpected-pivot</id><content type="html" xml:base="https://klevo.sk/projects/development-story-of-envirobly-and-the-unexpected-pivot/"><![CDATA[<p>Envirobly has been my three-year obsession: a control plane that wires into your AWS account to run services, databases and gateways with zero-downtime deploys and live observability. I <a href="/projects/envirobly-efficient-application-hosting-platform/">launched it</a> in October 2025 after iterating relentlessly—and then realized the story was only half told.</p>

<p>This post documents what shipped along the way and why I’m pivoting toward an open source, bare metal and VPS friendly future.</p>

<p><img src="/uploads/2025/12/stay-or-pivot-1.webp" alt="Stay or pivot signpost" /></p>

<!--more-->

<h2 id="2022--first-aws-experiments">2022 – First AWS experiments</h2>
<ul>
  <li>October 2022: <code class="language-plaintext highlighter-rouge">rails new</code>, AWS SDK wiring and CloudFormation EventBridge ingestion so stacks/resources could be mirrored in the app.</li>
  <li>Users, projects, resources, and dashboards established the “account → project → stack” model with a UI for streaming stack events.</li>
  <li>Stack templates, sample event ingestion, and automatic region discovery created a thin but working AWS control plane by November.</li>
</ul>

<h2 id="2023--from-stacks-to-services">2023 – From stacks to services</h2>
<ul>
  <li>Services and environs arrived with domains, shared instances, autoscaling containers, CloudWatch logging, build log streaming and an auto-deploy-from-branch UI (May 2023).</li>
  <li>Deployments matured: completed builds fed deployments (July 2023); long-running workflows gained a custom stepped-action engine with checksum + concurrency controls (December 2023).</li>
  <li>Config delivery moved to S3 and instance metadata, keeping hosts immutable while letting services update quickly.</li>
</ul>

<h2 id="early-2024--data-durability-and-foundations">Early 2024 – Data durability and foundations</h2>
<ul>
  <li>Universal AMIs for builders/runners (January 2024).</li>
  <li>OpenZFS-backed data volumes with backup/restore and host orchestration for service EBS backups made persistent workloads first-class.</li>
  <li>Service data-volume mounts, backup scheduling, and stepped “trash” flows reduced risk when deleting or rolling back services.</li>
</ul>

<h2 id="spring-2024--observability-and-networking-polish">Spring 2024 – Observability and networking polish</h2>
<ul>
  <li>Prometheus metrics for services/gateways plus Traefik router metrics and edge log collection delivered unified dashboards.</li>
  <li>Networking hardened with ACME-issued vanity domains, DNS/SSL automation, and the ability to scale gateways to zero when idle.</li>
</ul>

<h2 id="summer-2024--deployment-pipeline-hardening">Summer 2024 – Deployment pipeline hardening</h2>
<ul>
  <li>Zero-downtime improvements for deploys and an API/CLI-friendly deployment surface replaced earlier experiments.</li>
  <li>Config deployments covered all service attributes and a stepped post-deploy cleanup deleted build contexts to prevent cache drift.</li>
</ul>

<h2 id="fall-2024--cli-first-developer-workflow">Fall 2024 – CLI-first developer workflow</h2>
<ul>
  <li>UI-based service provisioning was removed in favor of pure config-backed configuration, powered by <a href="https://github.com/envirobly/envirobly-cli">envirobly CLI</a>.</li>
</ul>

<h2 id="2025--resilience-ux-launch">2025 – Resilience, UX, launch</h2>
<ul>
  <li>Storage/backup track: OpenZFS 2.3.0, Docker registry cache (instead of snapshot-based build caching), striped ZFS volumes for maximum EBS IOPs and throughput, snapshot backups, and cross-region restores, plus a UI for scheduling backups and browsing deleted services.</li>
  <li>Developer workflow: git-object push/pull baked into <code class="language-plaintext highlighter-rouge">envirobly-cli</code> culminated in CLI v1.0.0, with UI deploy/exec snippets for copy/paste ease.</li>
  <li>Stripe billing, per-profile access tokens, and Turnstile-protected signups tightened edges.</li>
  <li><strong>Launched to production in early October 2025</strong> 🎉🥳</li>
</ul>

<h2 id="why-pivot-after-launch">Why pivot after launch</h2>
<ul>
  <li>Despite launch traffic, early interest relied on me constantly posting on X; it was exhausting and still yielded zero paying customers.</li>
  <li>Despite all the cost optimization, AWS is still about 10x more expensive compared to bare metal or VPS from vendors like Hetzner or OVH. Small to medium customers and solo developers are my main target audience. I didn’t want to face this discrepancy until I basically finished the product 🤪</li>
  <li>An open-source release, in theory, promises “passive” discovery via GitHub, backlinks and community contributions that marketing alone couldn’t sustain.</li>
  <li>Keeping the old product live would create false expectations, so I <a href="https://envirobly.com">replaced it with a clear pivot notice and a waiting list</a>.</li>
  <li>It’s early, there are no paying customers to disappoint. Better now then waiting and hoping for things to change.</li>
</ul>

<h2 id="the-unexpected-pivot-open-source-and-bare-metal">The unexpected pivot: open source and bare metal</h2>
<ul>
  <li>Direction: shift Envirobly to an open source model optimized for bare metal and VPS deployments, while keeping a hosted “convenience” option from the original author. Hopefully this will help with building trust, squash any vendor lock-in fears and open the door for community contributions that can even make it into the SAAS version, ala <a href="https://www.fizzy.do/">Fizzy</a>.</li>
  <li>Value prop: run on your own servers with live replication, so stateful apps can migrate quickly during failures or upgrades. “Your app, your servers, with a safety net.”</li>
  <li>Messaging: a concise landing update describing the pivot, a call to join the waiting list, and no lengthy feature promises until the new product is ready.</li>
  <li>License stance: MIT or <a href="https://github.com/basecamp/fizzy?tab=License-1-ov-file#readme">O’Saasy</a>. I’ll decide as I go along and things crystallize.</li>
  <li>Next steps: start the open source version, validate the bare metal/VPS workflows in public and relaunch with a single, focused story instead of maintaining the legacy AWS path I lost faith in.</li>
</ul>

<p><img src="/uploads/2025/12/decided-to-pivot.webp" alt="Stay or pivot path" /></p>]]></content><author><name>Robert Starsi</name></author><category term="personal" /><summary type="html"><![CDATA[Envirobly has been my three-year obsession: a control plane that wires into your AWS account to run services, databases and gateways with zero-downtime deploys and live observability. I launched it in October 2025 after iterating relentlessly—and then realized the story was only half told. This post documents what shipped along the way and why I’m pivoting toward an open source, bare metal and VPS friendly future.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://klevo.sk/uploads/2025/12/stay-or-pivot-1.webp" /><media:content medium="image" url="https://klevo.sk/uploads/2025/12/stay-or-pivot-1.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Jekyll static site on Cloudflare Workers</title><link href="https://klevo.sk/devops/jekyll-static-site-on-cloudflare-workers/" rel="alternate" type="text/html" title="Jekyll static site on Cloudflare Workers" /><published>2025-12-08T07:00:00+00:00</published><updated>2025-12-08T07:00:00+00:00</updated><id>https://klevo.sk/devops/jekyll-static-site-on-cloudflare-workers</id><content type="html" xml:base="https://klevo.sk/devops/jekyll-static-site-on-cloudflare-workers/"><![CDATA[<p>Cloudflare Workers can serve a Jekyll site straight from the built <code class="language-plaintext highlighter-rouge">_site</code> folder, which allows you to deploy and <a href="https://developers.cloudflare.com/workers/platform/pricing/#workers">run a static site for free</a>.</p>

<p>It seems Cloudflare is shifting focus from Pages to Workers, since the creation of a new Page app is now almost hidden, behind a small link on the bottom of a new Worker screen. Thankfully, using Workers is almost as simple.</p>

<!--more-->

<p>Once you create a new worker application and connect your repo, this is how to configure it:</p>

<p><img src="/uploads/2025/12/set-up-worker.png" alt="" /></p>

<p>The important part is <code class="language-plaintext highlighter-rouge">bundle exec jekyll build</code> for the build command. The deploy command is the default <code class="language-plaintext highlighter-rouge">npx wrangler deploy</code>.</p>

<p>Before your first deploy, create <code class="language-plaintext highlighter-rouge">wrangler.toml</code> in the root of your repo with the following content:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">name</span> <span class="o">=</span><span class="w"> </span><span class="s">"my-static-site"</span>
<span class="n">compatibility_date</span> <span class="o">=</span><span class="w"> </span><span class="s">"2025-04-01"</span>

<span class="k">[</span><span class="n">assets</span><span class="k">]</span>
<span class="n">directory</span> <span class="o">=</span><span class="w"> </span><span class="s">"./_site/"</span>
</code></pre></div></div>

<p><strong>Important:</strong> Do not include <code class="language-plaintext highlighter-rouge">binding</code> if you want to keep running your site for free. That invokes worker CPU cycles, which you eventually pay for.</p>

<p>Also adjust Jekyll’s <code class="language-plaintext highlighter-rouge">_config.yml</code> to ignore this file:</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">exclude</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">wrangler.toml</span>
</code></pre></div></div>

<p>For local previews run <code class="language-plaintext highlighter-rouge">npx wrangler dev</code> after a build and it will serve from <code class="language-plaintext highlighter-rouge">_site</code>. Add routes or a custom domain later by extending the <a href="https://developers.cloudflare.com/workers/wrangler/configuration">config</a>.</p>]]></content><author><name>Robert Starsi</name></author><category term="devops" /><summary type="html"><![CDATA[Cloudflare Workers can serve a Jekyll site straight from the built _site folder, which allows you to deploy and run a static site for free. It seems Cloudflare is shifting focus from Pages to Workers, since the creation of a new Page app is now almost hidden, behind a small link on the bottom of a new Worker screen. Thankfully, using Workers is almost as simple.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://klevo.sk/uploads/2025/12/set-up-worker.png" /><media:content medium="image" url="https://klevo.sk/uploads/2025/12/set-up-worker.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">locport v1.2.0 released with listening port indicators</title><link href="https://klevo.sk/open-source/locport-v1-2-0-released-with-listening-port-indicators/" rel="alternate" type="text/html" title="locport v1.2.0 released with listening port indicators" /><published>2025-11-09T13:14:33+00:00</published><updated>2025-11-09T13:14:33+00:00</updated><id>https://klevo.sk/open-source/locport-v1-2-0-released-with-listening-port-indicators</id><content type="html" xml:base="https://klevo.sk/open-source/locport-v1-2-0-released-with-listening-port-indicators/"><![CDATA[<p>I’m proud to release v1.2.0 of <a href="https://github.com/klevo/locport">locport</a>, my open-source tool to standardize localhost project port management.</p>

<p>This version introduces:</p>

<ul>
  <li>Neater listing of projects and their hostnames.</li>
  <li>Next to each hostname there is an <strong>indicator as to whether that port is listening</strong>, so you know which of your projects/apps are running.</li>
  <li>Port or host conflict messages point you to the file where the conflict originates.</li>
  <li>Automatic port assignment now checks for whether the selected port is not already listening, no matter whether indexed by locport.</li>
  <li>Test suite and CI coverage of the existing functionality.</li>
</ul>

<!--more-->

<p>To install the latest version simply:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem <span class="nb">install </span>locport
</code></pre></div></div>

<p>This is how the listing of projects and ports used, looks in the terminal:</p>

<p><img src="/uploads/2025/11/locport-list-1024x782.png" alt="" /></p>]]></content><author><name>Robert Starsi</name></author><category term="open-source" /><summary type="html"><![CDATA[I’m proud to release v1.2.0 of locport, my open-source tool to standardize localhost project port management. This version introduces: Neater listing of projects and their hostnames. Next to each hostname there is an indicator as to whether that port is listening, so you know which of your projects/apps are running. Port or host conflict messages point you to the file where the conflict originates. Automatic port assignment now checks for whether the selected port is not already listening, no matter whether indexed by locport. Test suite and CI coverage of the existing functionality.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://klevo.sk/uploads/2025/11/locport-list-1024x782.png" /><media:content medium="image" url="https://klevo.sk/uploads/2025/11/locport-list-1024x782.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">tmux: reload bash config across all panes and sessions</title><link href="https://klevo.sk/unix/tmux-reload-bash-config-across-all-panes-and-sessions/" rel="alternate" type="text/html" title="tmux: reload bash config across all panes and sessions" /><published>2025-11-07T08:18:40+00:00</published><updated>2025-11-07T08:18:40+00:00</updated><id>https://klevo.sk/unix/tmux-reload-bash-config-across-all-panes-and-sessions</id><content type="html" xml:base="https://klevo.sk/unix/tmux-reload-bash-config-across-all-panes-and-sessions/"><![CDATA[<p>Yesterday I got myself into <a href="https://github.com/tmux/tmux/wiki">tmux</a> and I love it!</p>

<blockquote>
  <p>tmux is a terminal multiplexer. It lets you switch easily between several programs in one terminal, detach them (they keep running in the background) and reattach them to a different terminal.</p>
</blockquote>

<p>I’ve been using <a href="https://zellij.dev/">Zellij</a> for several months prior to this. Which I must thank for getting me introduced to the world of terminal multiplexers. However I’ve been running into several issues with it, that got me looking for an alternative:</p>

<!--more-->

<ul>
  <li>Session manager entires keep shifting and popping in and out randomly. This is the most frustrating thing, probably number one cause for me to look for a change.</li>
  <li>Occasional crashes during session switching.</li>
  <li>No ability to toggle between last two used sessions.</li>
  <li>No number hotkey assignment for tabs.</li>
  <li>Need to match Zellij theme with your terminal theme. They are two separate things.</li>
</ul>

<h2 id="the-switch">The switch</h2>

<p>A fantastic start into tmux are these <a href="https://www.youtube.com/watch?v=niuOc02Rvrc">two videos from typecraft over at YouTube</a>. The basics are wonderfully explained there as well as some key configuration. This got me going immediately.</p>

<p>Here’s a documented <strong>.tmux.conf</strong> I arrived at after a day or two:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Change leader key to something more ergonomic</span>
<span class="c"># Note: On MacOS disable the shortcut to change keyboard input language first in Settings.</span>
<span class="nb">set</span> <span class="nt">-g</span> prefix C-Space
<span class="c"># Allow mouse interactions like focusing windows and panes and resizing</span>
<span class="nb">set</span> <span class="nt">-g</span> mouse on
<span class="c"># Simplify the status bar look: white text on black background</span>
<span class="nb">set</span> <span class="nt">-g</span> status-style <span class="nb">bg</span><span class="o">=</span>black,fg<span class="o">=</span>white
<span class="c"># Disable hostname, date and time display in the status bar</span>
<span class="nb">set</span> <span class="nt">-g</span> status-right <span class="s2">""</span>
<span class="nb">set</span> <span class="nt">-g</span> status-position top
<span class="c"># Use vim style navigation</span>
<span class="nb">set</span> <span class="nt">-g</span> mode-keys vi
<span class="c"># Number windows starting from 1, instead of 0</span>
<span class="nb">set</span> <span class="nt">-g</span> base-index 1
<span class="c"># When window is closed, refresh the numbering</span>
<span class="nb">set</span> <span class="nt">-g</span> renumber-windows on
<span class="c"># Change the look of session name in the status bar</span>
<span class="nb">set</span> <span class="nt">-g</span> status-left <span class="s2">"#[bg=#9A7122,bold] #S #[default]#[fg=#9A7122]#[default] "</span>
<span class="c"># Do not truncate session names too early</span>
<span class="nb">set</span> <span class="nt">-g</span> status-left-length 24
<span class="c"># Pane borders</span>
<span class="nb">set</span> <span class="nt">-g</span> pane-border-style <span class="nb">fg</span><span class="o">=</span><span class="s2">"#5A5A5A"</span>
<span class="nb">set</span> <span class="nt">-g</span> pane-active-border-style <span class="nb">fg</span><span class="o">=</span><span class="s2">"#B3811F"</span>

<span class="c"># Shortcut for live reload of tmux config</span>
unbind r
<span class="nb">bind </span>r source-file ~/.tmux.conf

<span class="c"># Last session toggle</span>
<span class="nb">bind </span>S switch-client <span class="nt">-l</span>

<span class="c"># Maximize pane</span>
<span class="nb">bind </span>z resize-pane <span class="nt">-Z</span>

<span class="c"># vim like pane navigation</span>
<span class="nb">bind </span>h <span class="k">select</span><span class="nt">-pane</span> <span class="nt">-L</span>
<span class="nb">bind </span>j <span class="k">select</span><span class="nt">-pane</span> <span class="nt">-D</span>
<span class="nb">bind </span>k <span class="k">select</span><span class="nt">-pane</span> <span class="nt">-U</span>
<span class="nb">bind </span>l <span class="k">select</span><span class="nt">-pane</span> <span class="nt">-R</span>

<span class="c"># Set new windows and panes in the current directory</span>
<span class="nb">bind </span>c new-window <span class="nt">-c</span> <span class="s2">"#{pane_current_path}"</span>
<span class="nb">bind</span> <span class="s1">'"'</span> split-window <span class="nt">-c</span> <span class="s2">"#{pane_current_path}"</span>
<span class="nb">bind</span> % split-window <span class="nt">-h</span> <span class="nt">-c</span> <span class="s2">"#{pane_current_path}"</span>
</code></pre></div></div>

<h2 id="superpowers-issuing-commands-to-all-panes-in-all-sessions">Superpowers: issuing commands to all panes in all sessions</h2>

<p>I almost forgot what I wanted to write about initially. So here’s a ready made alias I use to reload bash config across all my tmux bash sessions. This is sooo useful when I for example add something like a new alias in my <strong>.bashrc</strong>. Previously I’d have to manually source the config in each pane where I’d want to use the alias, which is a pain. Now it’s just one command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">alias </span>reload-bash-config-in-bash-panes<span class="o">=</span><span class="s1">$'tmux list-panes -a -F </span><span class="se">\'</span><span class="s1">#{session_name}:#{window_index}.#{pane_index} #{pane_current_command}</span><span class="se">\'</span><span class="s1"> | awk </span><span class="se">\'</span><span class="s1">$2 == "bash" {print $1}</span><span class="se">\'</span><span class="s1"> | xargs -I{} tmux send-keys -t {} "source ~/.bashrc" C-m'</span>
</code></pre></div></div>

<p>This ensures the source command is only executed in panes that are running “bash”. It won’t affect your neovim instances for example, or other programs where it could cause unintended consequences.</p>

<h2 id="layouts-gitops-for-workspaces">Layouts: GitOps for workspaces</h2>

<p>In Zellij, layouts are build in and a wonderful feature. In tmux land, there is <a href="https://github.com/tmuxinator/tmuxinator">tmuxinator</a> - a separate program - that handles layout definition via YAML files and starting sessions based on those layouts. It’s simple and I got going with it immediately.</p>

<p>Here’s a layout I use to run this blog in development environment:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">klevo</span>
<span class="na">root</span><span class="pi">:</span> <span class="s">~/projects/klevo</span>

<span class="na">windows</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">edit</span><span class="pi">:</span>
      <span class="na">layout</span><span class="pi">:</span> <span class="s">main-vertical</span>
      <span class="na">panes</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="c1">#bash</span>
        <span class="pi">-</span> <span class="s">nvim</span>
  <span class="pi">-</span> <span class="na">server</span><span class="pi">:</span>
      <span class="na">layout</span><span class="pi">:</span> <span class="s">even-horizontal</span>
      <span class="na">panes</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">docker compose up</span>
        <span class="pi">-</span> <span class="s">ruby -rwebrick -e'WEBrick::HTTPServer.new(:Port =&gt; 8083, :DocumentRoot =&gt; ("#{Dir.pwd}/_site")).start'</span>
</code></pre></div></div>

<p>tmux and neovim are the two things I wish I learned much earlier in my career. Mastering these tools provided an immense ergonomic boost to my workflows. It’s a joy to fly through projects and tasks in a fast, consistent, repeatable and simple environment, exactly suited to my needs.</p>

<h2 id="this-is-how-it-looks">This is how it looks</h2>

<p>Simple, elegant, just a touch of color for the session name.</p>

<p><img src="/uploads/2025/11/Screenshot-2025-11-07-at-09.27.32-1024x615.png" alt="My current tmux configuration running inside Ghostty terminal." /></p>]]></content><author><name>Robert Starsi</name></author><category term="open-source" /><category term="unix" /><category term="bash" /><category term="terminal" /><category term="tmux" /><category term="zellij" /><summary type="html"><![CDATA[Yesterday I got myself into tmux and I love it! tmux is a terminal multiplexer. It lets you switch easily between several programs in one terminal, detach them (they keep running in the background) and reattach them to a different terminal. I’ve been using Zellij for several months prior to this. Which I must thank for getting me introduced to the world of terminal multiplexers. However I’ve been running into several issues with it, that got me looking for an alternative:]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://klevo.sk/uploads/2025/11/Screenshot-2025-11-07-at-09.27.32-1024x615.png" /><media:content medium="image" url="https://klevo.sk/uploads/2025/11/Screenshot-2025-11-07-at-09.27.32-1024x615.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Enable IPv6 connectivity to and from containers deployed with Kamal</title><link href="https://klevo.sk/unix/enable-ipv6-connectivity-from-containers-deployed-with-kamal/" rel="alternate" type="text/html" title="Enable IPv6 connectivity to and from containers deployed with Kamal" /><published>2025-11-04T14:57:44+00:00</published><updated>2025-11-04T14:57:44+00:00</updated><id>https://klevo.sk/unix/enable-ipv6-connectivity-from-containers-deployed-with-kamal</id><content type="html" xml:base="https://klevo.sk/unix/enable-ipv6-connectivity-from-containers-deployed-with-kamal/"><![CDATA[<p>At the moment, <a href="https://github.com/basecamp/kamal">Kamal</a> doesn’t support IPv6 out of the box. Meaning your containers won’t be able to reach IPv6 hosts at all. IPv6 support is expanding, and on some of my projects, I need such connectivity. Taking Kamal for a ride, I’ve run into this issue and had to solve it.</p>

<!--more-->

<p>I’ll show you how to do this, both with an already deployed app, as well as if you haven’t run “kamal setup” just yet.</p>

<h2 id="already-deployed-app">Already deployed app</h2>

<p>There will be a short downtime (couple of seconds) while you do the switch. So plan for that.</p>

<p>All we need to do is to recreate the <strong>kamal</strong> Docker network, which is created with IPv4 support only, by Kamal. The problem is, you can’t remove a Docker network if containers are attached to it.</p>

<p>Here’s a script that reconnects the containers to a temporary network, recreates kamal network with IPv6 support, connects the containers back to it and cleans up. During this, there will be a few seconds of a downtime, as your containers will briefly loose connection to the network and/or between each other.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="nb">set</span> <span class="nt">-euo</span> pipefail

<span class="nv">OLD_NET</span><span class="o">=</span><span class="s2">"kamal"</span>
<span class="nv">TEMP_NET</span><span class="o">=</span><span class="s2">"kamal_tmp_v6"</span>

<span class="nb">echo</span> <span class="s2">"Collecting containers connected to network '</span><span class="nv">$OLD_NET</span><span class="s2">'..."</span>
<span class="nv">CONTAINERS</span><span class="o">=</span><span class="si">$(</span>docker network inspect <span class="s2">"</span><span class="nv">$OLD_NET</span><span class="s2">"</span> <span class="nt">-f</span> <span class="s1">'{{range .Containers}}{{.Name}} {{end}}'</span><span class="si">)</span>

<span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$CONTAINERS</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"No containers found on network '</span><span class="nv">$OLD_NET</span><span class="s2">'."</span>
  <span class="nb">exit </span>1
<span class="k">fi

</span><span class="nb">echo</span> <span class="s2">"Creating temporary IPv6-enabled network '</span><span class="nv">$TEMP_NET</span><span class="s2">'..."</span>
docker network create <span class="se">\</span>
  <span class="nt">--ipv6</span> <span class="se">\</span>
  <span class="s2">"</span><span class="nv">$TEMP_NET</span><span class="s2">"</span>

<span class="nb">echo</span> <span class="s2">"Connecting containers to temporary network..."</span>
<span class="k">for </span>c <span class="k">in</span> <span class="nv">$CONTAINERS</span><span class="p">;</span> <span class="k">do
  </span><span class="nb">echo</span> <span class="s2">" → Connecting </span><span class="nv">$c</span><span class="s2"> to </span><span class="nv">$TEMP_NET</span><span class="s2">"</span>
  docker network connect <span class="s2">"</span><span class="nv">$TEMP_NET</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$c</span><span class="s2">"</span>
<span class="k">done

</span><span class="nb">echo</span> <span class="s2">"Disconnecting containers from old network '</span><span class="nv">$OLD_NET</span><span class="s2">'..."</span>
<span class="k">for </span>c <span class="k">in</span> <span class="nv">$CONTAINERS</span><span class="p">;</span> <span class="k">do
  </span><span class="nb">echo</span> <span class="s2">" → Disconnecting </span><span class="nv">$c</span><span class="s2"> from </span><span class="nv">$OLD_NET</span><span class="s2">"</span>
  docker network disconnect <span class="s2">"</span><span class="nv">$OLD_NET</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$c</span><span class="s2">"</span> <span class="o">||</span> <span class="nb">true
</span><span class="k">done

</span><span class="nb">echo</span> <span class="s2">"Removing old network '</span><span class="nv">$OLD_NET</span><span class="s2">'..."</span>
docker network <span class="nb">rm</span> <span class="s2">"</span><span class="nv">$OLD_NET</span><span class="s2">"</span>

<span class="nb">echo</span> <span class="s2">"Recreating network '</span><span class="nv">$OLD_NET</span><span class="s2">' with IPv6 enabled..."</span>
docker network create <span class="se">\</span>
  <span class="nt">--ipv6</span> <span class="se">\</span>
  <span class="s2">"</span><span class="nv">$OLD_NET</span><span class="s2">"</span>

<span class="nb">echo</span> <span class="s2">"Reconnecting containers to new '</span><span class="nv">$OLD_NET</span><span class="s2">'..."</span>
<span class="k">for </span>c <span class="k">in</span> <span class="nv">$CONTAINERS</span><span class="p">;</span> <span class="k">do
  </span><span class="nb">echo</span> <span class="s2">" → Connecting </span><span class="nv">$c</span><span class="s2"> to </span><span class="nv">$OLD_NET</span><span class="s2">"</span>
  docker network connect <span class="s2">"</span><span class="nv">$OLD_NET</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$c</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">" → Disconnecting </span><span class="nv">$c</span><span class="s2"> from </span><span class="nv">$TEMP_NET</span><span class="s2">"</span>
  docker network disconnect <span class="s2">"</span><span class="nv">$TEMP_NET</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$c</span><span class="s2">"</span> <span class="o">||</span> <span class="nb">true
</span><span class="k">done

</span><span class="nb">echo</span> <span class="s2">"Cleaning up temporary network '</span><span class="nv">$TEMP_NET</span><span class="s2">'..."</span>
docker network <span class="nb">rm</span> <span class="s2">"</span><span class="nv">$TEMP_NET</span><span class="s2">"</span>

<span class="nb">echo</span> <span class="s2">"✅ IPv6 successfully enabled on '</span><span class="nv">$OLD_NET</span><span class="s2">' and containers reattached."</span>
</code></pre></div></div>

<p>Copy it anywhere on your server, for example <strong>~/enable_ipv6_on_kamal.sh</strong>, make it executable and run it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">chmod</span> +x enable_ipv6_on_kamal.sh

<span class="c"># Run it</span>
./enable_ipv6_on_kamal.sh
</code></pre></div></div>

<p>You’re done. After a few seconds you should be able to access your app, as kamal-proxy succeeds with it’s health checks.</p>

<h2 id="before-your-first-deployment">Before your first deployment</h2>

<p>This is easier. On the host, just create the Docker network Kamal expects manually, and Kamal will use that once you start deploying.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker network create <span class="nt">--ipv6</span> kamal 
</code></pre></div></div>

<p>That’s all! Now you can run <strong>kamal setup</strong> from your developer machine and you’ll have IPv6 connectivity from and to your containers right away.</p>]]></content><author><name>Robert Starsi</name></author><category term="docker" /><category term="ruby-and-rails" /><category term="unix" /><category term="ipv6" /><category term="kamal" /><summary type="html"><![CDATA[At the moment, Kamal doesn’t support IPv6 out of the box. Meaning your containers won’t be able to reach IPv6 hosts at all. IPv6 support is expanding, and on some of my projects, I need such connectivity. Taking Kamal for a ride, I’ve run into this issue and had to solve it.]]></summary></entry><entry><title type="html">Enabling initramfs on AWS EC2</title><link href="https://klevo.sk/unix/enabling-initramfs-on-aws-ec2/" rel="alternate" type="text/html" title="Enabling initramfs on AWS EC2" /><published>2025-11-03T15:59:09+00:00</published><updated>2025-11-03T15:59:09+00:00</updated><id>https://klevo.sk/unix/enabling-initramfs-on-aws-ec2</id><content type="html" xml:base="https://klevo.sk/unix/enabling-initramfs-on-aws-ec2/"><![CDATA[<p>Modern EC2 instances on AWS boot <strong>without</strong> invoking <strong>initramfs</strong> by default. So if you want to do things like repartition your disk, where the root partition is, you’ll be scratching your head why your initramfs hooks aren’t loading.</p>

<!--more-->

<p>After an hour or two of debugging, I figured out an easy way to enable it. This is tested on Ubuntu 24.04 LTS:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo rm</span> /etc/default/grub.d/40-force-partuuid.cfg
<span class="nb">sudo </span>update-grub
<span class="nb">sudo </span>grub-mkconfig <span class="nt">-o</span> /boot/grub/grub.cfg

<span class="c"># On the next reboot initramfs will run</span>
<span class="nb">sudo </span>reboot
</code></pre></div></div>

<p>Enjoy.</p>]]></content><author><name>Robert Starsi</name></author><category term="unix" /><summary type="html"><![CDATA[Modern EC2 instances on AWS boot without invoking initramfs by default. So if you want to do things like repartition your disk, where the root partition is, you’ll be scratching your head why your initramfs hooks aren’t loading.]]></summary></entry><entry><title type="html">Envirobly – Efficient application hosting platform</title><link href="https://klevo.sk/projects/envirobly-efficient-application-hosting-platform/" rel="alternate" type="text/html" title="Envirobly – Efficient application hosting platform" /><published>2025-10-30T08:58:19+00:00</published><updated>2025-10-30T08:58:19+00:00</updated><id>https://klevo.sk/projects/envirobly-efficient-application-hosting-platform</id><content type="html" xml:base="https://klevo.sk/projects/envirobly-efficient-application-hosting-platform/"><![CDATA[<p><a href="https://envirobly.com/">Envirobly.com</a> is a startup I solo founded and launched in October 2025. It’s a culmination of an <a href="/projects/development-story-of-envirobly-and-the-unexpected-pivot/">intense 3 year work</a>, crafting a platform to deploy web applications to, while keeping the costs manageable.</p>

<p>It runs the full lifecycle of application infrastructure on AWS. It connects to a customer’s AWS account, lays down regional foundations (VPC, NAT, Traefik gateways, Route53 DNS, Managed Prometheus), and lets teams ship services, databases, and gateways with zero-downtime deploys, live logs and metrics.</p>

<!--more-->

<p>All of it done on pure EC2 instances, with custom AMI, without relying on expensive AWS services, like load balancers, RDS or paid VPC features.</p>

<h2 id="core-capabilities">Core capabilities</h2>
<ul>
  <li>Accounts, projects, and environs model real deployments: per-region plots provision VPCs/gateways; environs hold services, deployments, and logs.</li>
  <li>Service types: source-built or remote containers, PostgreSQL, and Valkey—all with autoscaling knobs, health checks, public/private routing, and CLI exec support.</li>
  <li>Deployments: build-on-deploy for source images, promotion/rollback handling, desired vs. present version tracking, and per-deployment log streams.</li>
  <li>Observability: CloudWatch log streaming with scoped STS tokens; AWS Managed Prometheus metrics for services and gateways, including Traefik router stats.</li>
  <li>Networking and security: vanity domains with ACME certificates, Route53 automation, TLS defaults, SSH via EC2 Instance Connect gateways, and per-resource access tokens.</li>
  <li>Data durability: OpenZFS-backed volumes on EBS with snapshots, daily backups, cross-region restore, and UI/CLI flows for backup browsing and restore.</li>
  <li>Operations UX: CLI (<code class="language-plaintext highlighter-rouge">envirobly</code>) for deploy/exec, Turbo-powered UI for live status, GoodJob-backed background orchestration, and Solid Cache-backed caching.</li>
  <li>Billing and access: Stripe subscriptions/charges, email verification/invites, Cloudflare Turnstile-protected signup, and profile/billing address management.</li>
</ul>

<h2 id="architecture-and-tech">Architecture and tech</h2>
<ul>
  <li>Rails 8, Postgres, GoodJob, Solid Cache, Hotwire (Turbo/Stimulus), Propshaft.</li>
  <li>AWS automation via CloudFormation stacks, STS federation for logs, EC2/EBS/Route53/ACM/Prometheus, Traefik ingress, custom distributed ACME handling, and OpenZFS for data volumes.</li>
  <li>Custom “Stepped Actions” orchestration layer coordinates long-running provision/trash/backup flows with checksums, concurrency keys, and broadcast updates. I plan to release this as open source in the future.</li>
</ul>

<h2 id="product-walkthrough">Product walkthrough</h2>

<h3 id="connect-your-aws-account">Connect your AWS account</h3>
<p>Envirobly links to your AWS account in seconds with a UI prompt and CLI handshake so foundations can be created in your own cloud.</p>

<p><img src="/uploads/envirobly-aws-screenshots/01-connect-an-account.png" alt="Connect an account in the UI" />
<img src="/uploads/envirobly-aws-screenshots/02-cli-connect.png" alt="CLI-based account handshake" />
<img src="/uploads/envirobly-aws-screenshots/03-connection-established.png" alt="Account connection established banner" /></p>

<h3 id="orchestrated-setup">Orchestrated setup</h3>
<p>Stepped actions show each CloudFormation and validation step so you always see progress on long-running provisioning work.</p>

<p><img src="/uploads/envirobly-aws-screenshots/04-stepped-actions.png" alt="Stepped action timeline in the UI" />
<img src="/uploads/envirobly-aws-screenshots/04a-stepped-actions.png" alt="Stepped action detail with completed steps" /></p>

<h3 id="projects-and-environs">Projects and environs</h3>
<p>Projects capture per-region foundations, while environs hold the services and routing that map to real deployments.</p>

<p><img src="/uploads/envirobly-aws-screenshots/05-new-project-creation.png" alt="New project creation flow" />
<img src="/uploads/envirobly-aws-screenshots/06-environs.png" alt="List of environs inside a project" /></p>

<h3 id="deploy-with-the-cli">Deploy with the CLI</h3>
<p>Source builds and remote containers deploy via the <code class="language-plaintext highlighter-rouge">envirobly</code> CLI, with status mirrored in the UI.</p>

<p><img src="/uploads/envirobly-aws-screenshots/07-cli-deployment.png" alt="CLI deploy command output" />
<img src="/uploads/envirobly-aws-screenshots/08-project-being-deployed.png" alt="Project deployment in progress" />
<img src="/uploads/envirobly-aws-screenshots/09-deployment-log.png" alt="Per-deployment log stream" />
<img src="/uploads/envirobly-aws-screenshots/10-deployment-complete.png" alt="Deployment completed successfully" /></p>

<h3 id="operate-services">Operate services</h3>
<p>Service views cover routing, scaling, metrics, logs, usage, backups, and custom domains—all backed by the AWS primitives noted above.</p>

<p><img src="/uploads/envirobly-aws-screenshots/11-service-details.png" alt="Service detail overview" />
<img src="/uploads/envirobly-aws-screenshots/12-service-metrics.png" alt="Service metrics charts" />
<img src="/uploads/envirobly-aws-screenshots/12a-service-metrics.png" alt="Expanded metrics detail" />
<img src="/uploads/envirobly-aws-screenshots/13-service-logs.png" alt="Live service logs" />
<img src="/uploads/envirobly-aws-screenshots/13-usage.png" alt="Usage snapshots for a service" />
<img src="/uploads/envirobly-aws-screenshots/14-service-backups.png" alt="Backup schedule and restore options" />
<img src="/uploads/envirobly-aws-screenshots/14-service-custom-domains.png" alt="Custom domains and TLS management" /></p>

<h2 id="mentions">Mentions</h2>

<ul>
  <li><a href="https://news.ycombinator.com/item?id=45488727">Hacker News discussion</a></li>
  <li><a href="https://weuserails.com/rails-web-apps/envirobly">We Use Rails listing</a></li>
</ul>

<h2 id="update-december-2025">Update (December 2025)</h2>

<p>I’m <a href="/projects/development-story-of-envirobly-and-the-unexpected-pivot/">changing the direction and pivoting</a>.</p>]]></content><author><name>Robert Starsi</name></author><category term="design" /><category term="html-css" /><category term="javascript" /><category term="projects" /><category term="ruby-and-rails" /><category term="html" /><category term="javascript" /><category term="paas" /><category term="rails" /><category term="ruby" /><summary type="html"><![CDATA[Envirobly.com is a startup I solo founded and launched in October 2025. It’s a culmination of an intense 3 year work, crafting a platform to deploy web applications to, while keeping the costs manageable. It runs the full lifecycle of application infrastructure on AWS. It connects to a customer’s AWS account, lays down regional foundations (VPC, NAT, Traefik gateways, Route53 DNS, Managed Prometheus), and lets teams ship services, databases, and gateways with zero-downtime deploys, live logs and metrics.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://klevo.sk/uploads/2025/10/envirobly-logo-2-380x380.webp" /><media:content medium="image" url="https://klevo.sk/uploads/2025/10/envirobly-logo-2-380x380.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>