<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[hexcode software]]></title><description><![CDATA[A general stack about anything IT related.]]></description><link>https://hexcode.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!x55o!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac7a4bef-512a-47d4-b3df-19c146606b3d_608x608.png</url><title>hexcode software</title><link>https://hexcode.substack.com</link></image><generator>Substack</generator><lastBuildDate>Thu, 09 Jul 2026 10:57:41 GMT</lastBuildDate><atom:link href="https://hexcode.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Alexander Weber]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[hexcode@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[hexcode@substack.com]]></itunes:email><itunes:name><![CDATA[Alexander]]></itunes:name></itunes:owner><itunes:author><![CDATA[Alexander]]></itunes:author><googleplay:owner><![CDATA[hexcode@substack.com]]></googleplay:owner><googleplay:email><![CDATA[hexcode@substack.com]]></googleplay:email><googleplay:author><![CDATA[Alexander]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[The Canary Is Singing Again]]></title><description><![CDATA[How a forgotten privacy tool got a major upgrade, and why you might need one]]></description><link>https://hexcode.substack.com/p/the-canary-is-singing-again</link><guid isPermaLink="false">https://hexcode.substack.com/p/the-canary-is-singing-again</guid><dc:creator><![CDATA[Alexander]]></dc:creator><pubDate>Mon, 26 Jan 2026 18:02:40 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5b271947-90f9-4dd4-a79b-99717514cbe2_832x1248.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Picture this: It's 2015. Edward Snowden has just blown the whistle on the NSA's global surveillance programs. Major Lazer &amp; DJ Snake release &#8220;Lean On&#8221; and the Electronic Frontier Foundation launches CanaryWatch. A service that tracks "warrant canaries" across the internet.</p><p>If you&#8217;re not familiar with warrant canaries, they&#8217;re brilliantly clever. Organizations publish signed statements saying they <em>haven&#8217;t</em> received secret government orders. Gag orders can prevent you from disclosing surveillance requests, but they can&#8217;t make you lie.</p><p>The beauty is in the negative space. If the canary stops singing, if the statement disappears or isn&#8217;t renewed, everyone knows something changed.</p><p>CanaryWatch became essential infrastructure. A central registry for monitoring these transparency signals. But like so many projects from that era, it eventually went quiet. The infrastructure aged. The tracking stopped. The canary itself went silent.</p><p>The need, however, never went away. In fact, the legal mechanisms compelling silence have only gotten more sophisticated. This is particularly true in jurisdictions that do not have the a 1st and/or 5th amendment protection that we have in the United States.</p><p><strong>Today, I&#8217;m launching Canary Command, a modern platform for creating, signing, publishing, and monitoring (warrant) canaries. At its heart is the Extensible Canary Format (ECF), a new open standard that fixes the real problems with existing canary implementations.</strong></p><h2><strong>The Problem With Today&#8217;s Warrant Canaries</strong></h2><p>Let me paint you a picture of the current state of warrant canaries.</p><p>Most are embarrassingly basic. A plain text file on a website, manually signed with GPG. No standardized format. No machine-verifiable structure. No automated verification. And absolutely no way for anyone to get notified when something changes.</p><p>Some organizations maintain them diligently. Others let them rot, forgotten files that become meaningless over time. There&#8217;s no ecosystem, no interoperability, no way to subscribe to updates.</p><p>This is infrastructure that should work even when you can&#8217;t speak. But the current implementations fail the moment someone gets busy or distracted.</p><p>Our tooling for privacy has not kept up.</p><p>I wanted to fix this and the result became the Extensible Canary Format (ECF) and the Canary Command platform.</p><h2><strong>ECF: Extensible Canary Format v1.0</strong></h2><p>At the heart of Canary Command is the <strong>Extensible Canary Format (ECF)</strong>. It&#8217;s a complete specification designed from scratch to solve the real problems with existing canary implementations.</p><p>ECF documents are plain text that anyone can read and understand. But they also contain structured metadata that makes them machine-verifiable. Here&#8217;s what a simple ECF canary looks like:</p><pre><code><code>--- BEGIN ECF STATEMENT ---

I, Jane Doe, hereby declare:
&#8226; I have NOT received any secret government orders
&#8226; I have NOT been compelled to provide access to user data
&#8226; I am making this statement of my own free will

This canary is valid until the expiration date specified in the metadata.

--- END ECF STATEMENT ---

--- BEGIN ECF METADATA (JSON) ---
{
    "version": "1.0-rc.1",
    "issued": {
        "by": ["Jane Doe"],
        "at": "2026-01-15T12:00:00Z"
    },
    "signers": [{
        "threshold": 1,
        "keys": [
            {
                "fingerprint": "A1B2 C3D4 E5F6 7890 1234 5678 9ABC DEF0 1234 5678",
                "weight": 1
            }
        ]
    }],
    "expires_at": "2026-02-15T12:00:00Z"
}
--- END ECF METADATA (JSON) ---</code></code></pre><p>The metadata uses standard formats (JSON, YAML, or TOML), making it trivial for automated systems to parse, verify, and monitor. ECF supports weighted multi-party signing, so organizations can require multiple executives to attest before a canary goes public.</p><h3><strong>The Timing Problem</strong></h3><p>Here&#8217;s a subtle but critical issue with traditional canaries: cryptographic signatures prove <em>who</em> signed a document, but not <em>when</em>.</p><p>An adversary who compromises a signing key could theoretically create backdated canaries. Without proof of timing, you can&#8217;t trust that a canary wasn&#8217;t forged after the fact.</p><p>ECF solves this with <strong>Proof of Recency</strong>, an optional section containing publicly verifiable timestamped data:</p><pre><code><code>--- BEGIN ECF PROOF OF RECENCY ---

Bitcoin block #925000 (2026-01-15):
Hash: 00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054

BBC News headline (2026-01-15 09:15 UTC):
"Tech leaders gather for annual summit in Davos"
https://www.bbc.com/news/technology-example

--- END ECF PROOF OF RECENCY ---
</code></code></pre><p>Including today&#8217;s Bitcoin block hash mathematically proves the document couldn&#8217;t have been created before that block was mined. It&#8217;s verifiable timestamping using public, immutable data.</p><p>On this platform, this proof data is a built-in feature. No manual work required. The platform supports Bitcoin blocks and news headlines from various RSS sources.</p><h3><strong>Multi-Person Verification</strong></h3><p>Corporate canaries often need multiple executives to sign off, ensuring no single person can be coerced into silence. ECF supports this with a weighted threshold system:</p><p>You can define multiple signers with different weights, set a threshold for validity, and even include &#8220;informational&#8221; signers who provide transparency but aren&#8217;t required.</p><p>For example, with keys weighted [3, 2, 1, 0] and a threshold of 3:</p><ul><li><p>The CEO (weight 3) can sign alone</p></li><li><p>The CTO (weight 2) + CISO (weight 1) can sign together</p></li><li><p>The PR director (weight 0) provides transparency but isn&#8217;t required</p></li></ul><p>This creates flexible governance models where different stakeholders have different levels of authority. Canaries stay in draft until the threshold is met, keeping everything private until fully authorized.</p><p>All signing thresholds in the signer array need to be met.</p><h3><strong>Total Cryptographic Integrity</strong></h3><p>ECF&#8217;s security model rests on one fundamental principle: <strong>every signature covers the entire document</strong>.</p><p>When you sign an ECF canary, you&#8217;re cryptographically binding to everything, the statement, the metadata, the authorized signers list, the expiry date, even the proof of recency data. It&#8217;s all covered by the signature.</p><p>This creates ironclad security guarantees:</p><p><strong>No backdating.</strong> The proof of recency is signed too. If it includes today&#8217;s Bitcoin block, the signature mathematically proves the document was created today.</p><p><strong>No signer manipulation.</strong> The authorized signers list is embedded and signed. A bad actor can&#8217;t add themselves without breaking existing signatures.</p><p><strong>No expiry tampering.</strong> The expiry date is signed content. To change validity periods, you need a new revision with all required signatures.</p><p><strong>No silent modifications.</strong> Any change, even a single character, invalidates all signatures. The cryptographic binding is absolute.</p><p>Multi-party signing provides mathematical security. Even if one signer is compromised, they can&#8217;t forge signatures from others, modify the document undetected, or backdate the canary.</p><p>The integrity comes from decentralization and mathematics, not trust. It is highly recommended to also split signers across jurisdictions, which is further explained in the FAQ.</p><h3><strong>Built to Evolve</strong></h3><p>ECF is designed to grow with future needs. Organizations can add custom extension sections without breaking the core specification:</p><pre><code><code>--- BEGIN ECF EXTENSION .v1.com.example.audit ---
[Custom audit data here]
--- END ECF EXTENSION .v1.com.example.audit ---
</code></code></pre><p>Extensions use inverted DNS naming to ensure global uniqueness. Parsers ignore extensions they don&#8217;t recognize, maintaining perfect forward compatibility.</p><div><hr></div><h2><strong>Canary Command: Making It Real</strong></h2><p>The ECF specification is completely open, anyone can implement it. But I also built <strong>Canary Command</strong>, a full-featured platform that makes warrant canaries accessible to everyone.</p><p>From individual journalists to companies, Canary Command handles the complexity so you can focus on what matters: maintaining your transparency signals.</p><h3><strong>Zero-Knowledge, Zero Compromise</strong></h3><p><strong>Your private keys never leave your GPG keyring.</strong> This wasn&#8217;t negotiable.</p><p>Canary Command&#8217;s desktop app (built with Tauri) invokes GPG commands directly on your machine. Private keys stay in GPG&#8217;s secure storage, never exported, never transmitted, never visible to our servers.</p><p>When you sign a canary:</p><ul><li><p>Isolated <code>gpg --detach-sign</code> command execution</p></li><li><p>Private keys remain in your GPG keyring</p></li><li><p>Only detached signatures and public keys go to servers</p></li><li><p>No mechanism exists to access your private key</p></li></ul><p>Even if our servers are compromised, your signing keys remain safe. It&#8217;s isolation between your keys and our infrastructure.</p><h3><strong>Two Ways to Deploy</strong></h3><p><strong>Hosted Canaries:</strong> Perfect for getting started. We store your ECF document and provide the full signing interface. Use the desktop app&#8217;s native GPG integration or paste signatures manually. We handle revision history, notifications, and subscriber management.</p><p><strong>External Canaries:</strong> For the control freaks. Host ECF documents on your own infrastructure, we fetch, verify, and monitor them. We become your independent watchdog. Signatures and public keys stay completely independent of platform accounts. Our domain verification ensures authenticity.</p><h3><strong>The Dead Man&#8217;s Switch (beta)</strong></h3><p>Here&#8217;s where it gets intense: <strong>poisoned keys</strong>.</p><p>A poisoned key is a pre-designated cryptographic dead man&#8217;s switch. If you&#8217;re coerced into signing while maintaining appearances, you sign with the poisoned key instead.</p><p>The canary immediately enters <strong>POISONED</strong> status, permanent, non-reversible, tamper-proof. All subscribers get instant alerts. The signal cannot be silenced or covered up.</p><p>Key properties:</p><ul><li><p><strong>Non-retroactive:</strong> Only affects future signatures; past canaries stay valid</p></li><li><p><strong>Permanent:</strong> Once poisoned, it cannot be unpoisoned, edited, or archived</p></li></ul><p><strong>Pro tip:</strong> Prepare a dedicated duress keypair in advance. If compelled to sign under observation, using this key silently triggers the emergency signal while appearing to comply.</p><p>This is feature is currently in beta testing but available. It will be just one among more designed to be a failsafe / anti-coercion safety net.</p><h3>The dual quorum rule</h3><p>One particularly powerful feature is the <strong>dual-quorum rule</strong>, which provides robust protection against hostile takeovers. Given that the platform knows about the canary history, we can enforce that changes to a canary follow the previous revision signing threshold to allow modification <em>and</em> the new revisions signing threshold to publish that revision out of draft status.</p><p>This feature prevents hostile takeovers due to the fact that the signer list being a part of the canary itself within the structure metadata.</p><h3><strong>Personal Canaries: When Individuals Need Them Most</strong></h3><p>Warrant canaries aren&#8217;t just for organizations. <strong>Personal canaries may be even more critical</strong> for individuals facing elevated risk.</p><p><strong>Journalists</strong> in hostile environments can maintain canaries affirming they haven&#8217;t been detained or had sources compromised. If a journalist goes silent, their canary&#8217;s expiration tells the story they cannot.</p><p><strong>Activists and dissidents</strong> can signal continued safety. A regularly renewed canary proves ongoing autonomy. Its absence speaks volumes.</p><p><strong>Whistleblowers</strong> can establish canaries before going public, creating cryptographic proof of their status. If targeted afterward, the canary becomes timeline evidence.</p><p><strong>Travelers</strong> can set short-duration canaries before crossing borders with aggressive device policies. If compelled to unlock devices or install surveillance, the canary expires or gets poisoned, alerting contacts without requiring communication.</p><p><strong>Security researchers</strong> can maintain canaries affirming they haven&#8217;t been approached to introduce backdoors.</p><p>The key insight: a personal canary isn&#8217;t just about legal orders. It&#8217;s <strong>proof-of-life for your digital autonomy</strong>. When you can no longer freely make the statement, the canary&#8217;s silence speaks for you.</p><p>The use-cases above and more are supported from the start. Your canary, your statement.</p><h3><strong>Teams and Individuals</strong></h3><p>Canary Command supports both personal workspaces (for individuals, activists, journalists) and shared workspaces (for organizations and teams). Multi-party signing enables secure collaboration with cryptographic accountability.</p><p>Personal workspaces are created automatically, sole access, full control. Shared workspaces use invite codes for team collaboration. You can import canaries between workspaces, preserving all history and subscriber relationships.</p><p>Draft management keeps revisions private until fully signed and approved.</p><h3><strong>Professional Templates</strong></h3><p>Not everyone knows how to write a legally sound warrant canary. We provide curated templates covering:</p><ul><li><p>Standard warrant canaries for organizations</p></li><li><p>Transparency reports with request tracking</p></li><li><p>Open source project canaries</p></li><li><p>Privacy service templates for VPNs</p></li><li><p>Personal safety templates for individuals</p></li><li><p>Press freedom affirmations</p></li><li><p>Australia compliant canaries (in theory)</p></li></ul><p>Each includes customizable variables, optional clauses, and live preview as you build your statement.</p><p><strong>Legal Disclaimer<br></strong>The templates mentioned above are recommended for educational purpose. For more information, consult a specialized attorney in your jurisdiction.</p><h3><strong>Stay Informed, Stay Safe</strong></h3><p>Anyone can subscribe to public canaries and receive notifications when:</p><ul><li><p>New revisions are published</p></li><li><p>Canaries approach expiration (configurable warnings)</p></li><li><p>Status changes from active to warning to expired</p></li><li><p><strong>A canary gets poisoned</strong> (immediate red alert)</p></li></ul><p>Delivery options include E-Mail, Telegram bot, and webhooks for integration with monitoring infrastructure. Never miss a critical update again.</p><h3><strong>Complete Audit Trail</strong></h3><p>Every change creates a new <strong>revision</strong>, an immutable ECF snapshot with full cryptographic integrity. Comprehensive audit capabilities include:</p><ul><li><p>Complete version history with diffs and syntax highlighting</p></li><li><p>Side-by-side revision comparison</p></li><li><p>Downloadable verification bundles for offline validation</p></li><li><p>Full signer attribution and timestamps</p></li><li><p>Immutable published revisions (permanent records)</p></li><li><p>Flexible draft management</p></li></ul><p>This creates a cryptographically verifiable chain of custody for every transparency statement.</p><h3><strong>Analytics Dashboard</strong></h3><p>Understand your canaries&#8217; reach and health:</p><ul><li><p>Subscriber counts per canary</p></li><li><p>Days since last update</p></li><li><p>Status and time until expiry</p></li><li><p>Revision history summaries</p></li><li><p>Aggregate statistics across all canaries</p></li></ul><div><hr></div><h2><strong>Desktop App: Cryptography Made Simple</strong></h2><p>The web interface works great and is fully features. However, for a more convenient way of publishing canaries, there&#8217;s a native desktop application built with Tauri. It provides direct cryptographic operations with zero compromises.</p><p>The desktop app:</p><ul><li><p>Automatically detects and integrates with your GPG installation</p></li><li><p>One-click ECF signing with isolated <code>gpg --detach-sign</code> execution</p></li><li><p>Zero private key export, keys never leave GPG&#8217;s secure storage</p></li><li><p>Real-time GPG status monitoring and debug logging</p></li><li><p>Cross-platform compatibility (macOS, Windows, Linux)</p></li></ul><p>GPG-optional by design: without GPG, paste signatures manually. With GPG, get seamless integration and one-click signing. Cryptographic operations become as simple as clicking a button.</p><div><hr></div><h2><strong>Security: Your Keys, Your Responsibility</strong></h2><p>Canary Command includes comprehensive security documentation, but remember: <strong>the system is only as secure as your key management practices</strong>.</p><h3><strong>Key Storage Options</strong></h3><p>For maximum security, consider:</p><ul><li><p><strong>Hardware security keys</strong> (YubiKey, Nitrokey), private keys never leave the device</p></li><li><p><strong>Air-gapped machines</strong>, dedicated signing computers never connected to networks</p></li><li><p><strong>Encrypted external media</strong>, keys on LUKS/VeraCrypt-encrypted drives</p></li><li><p><strong>Full disk encryption</strong> as minimum baseline</p></li></ul><h3><strong>Geographic Distribution</strong></h3><p>Distribute key backups across multiple jurisdictions to resist single-point seizure. For advanced protection, use <strong>Shamir&#8217;s Secret Sharing</strong>, mathematically split keys into shares requiring multiple pieces for reconstruction.</p><h3><strong>Operational Security Basics</strong></h3><ul><li><p>Prepare dedicated duress keypairs for emergency signaling</p></li><li><p>Plan for regular key rotation and lifecycle management</p></li><li><p>Use multi-person signing to avoid single points of failure</p></li><li><p>Regularly test backup restoration procedures</p></li></ul><h3><strong>Legal Considerations</strong></h3><p>Documentation covers password vs. biometric protection, search warrant preparation, and secure key deletion. <strong>Always consult digital rights attorneys for jurisdiction-specific advice.</strong> This is educational, professional legal counsel is essential for high-risk scenarios.</p><div><hr></div><h2><strong>Open Source, Open Standard</strong></h2><p>ECF is completely open source. I&#8217;m releasing the specification publicly because warrant canaries only work if the format is trustworthy, and trustworthiness requires transparency.</p><p>You don&#8217;t need Canary Command to use ECF. Parse it yourself. Build your own tools. The specification includes complete ABNF &amp; EBNF grammars, detailed schemas, verification procedures, and security considerations.</p><p>Full specification: <a href="https://canarycommand.com/ecf-standard.md">canarycommand.com/ecf-standard.md</a></p><div><hr></div><h2><strong>Carrying the Torch</strong></h2><p>Canary Command isn&#8217;t affiliated with the EFF or CanaryWatch, but it exists because they showed us what was possible and necessary.</p><p>When CanaryWatch went quiet, it revealed a critical gap in digital privacy infrastructure. Warrant canaries haven&#8217;t become less important; the surveillance age has made them more essential than ever.</p><p>This is my contribution to that legacy: modern cryptographic infrastructure for transparency statements, built with the rigor the problem deserves.</p><h2><strong>What&#8217;s Coming Next</strong></h2><p>We&#8217;re actively building features to make Canary Command even more powerful. Here&#8217;s what&#8217;s planned:</p><h3><strong>Duress Passwords</strong></h3><p>Extending the poisoned key concept: secondary authentication credentials that trigger silent alarms. Use your duress password under observation, and the system appears normal while silently alerting subscribers. The digital equivalent of a silent alarm.</p><h3><strong>Mobile App</strong></h3><p>A native iOS app for high-risk individuals who need to manage canaries on the go:</p><ul><li><p>One-tap renewal when time is limited</p></li><li><p>Biometric + PIN authentication (with disable option for high-security)</p></li><li><p>Offline signing with sync when connectivity returns</p></li><li><p>Secure enclave integration for hardware-backed key storage</p></li><li><p>Travel mode with decoy interface for border crossings</p></li><li><p>Push notifications for subscribed canary alerts</p></li></ul><p>Critical for journalists, activists, and travelers who can&#8217;t always access desktop machines when they need to renew or signal danger.</p><h2><strong>The Canary Is Singing Again</strong></h2><p><strong>Canary Command is live. Start yours today.</strong></p><ul><li><p><strong>Create your canary:</strong> <a href="https://canarycommand.com/">canarycommand.com</a></p></li><li><p><strong>Read more on:</strong> <a href="https://canarycommand.com/faq">canarycommand.com/faq</a></p></li><li><p><strong>Read the spec:</strong> <a href="https://canarycommand.com/ecf-standard.md">Full ECF standard online</a></p></li><li><p><strong>Download the desktop app:</strong> Native GPG integration</p></li><li><p><strong>Subscribe to canaries:</strong> Monitor what matters to you</p></li><li><p><strong>Explore public examples:</strong> <a href="https://canarycommand.com/">canarycommand.com</a></p></li><li><p><strong>Explore <a href="https://canarycommand.com/p/6dfbccb0-a62e-4164-a5ac-6ec5467cf2a2">the platform canary</a></strong></p></li></ul><p>In a world of secret orders and gag provisions, cryptographic transparency isn&#8217;t optional, it&#8217;s essential.</p><p><strong>The canary is singing again. Join the movement.</strong></p><div><hr></div><p><em>If you run a VPN, privacy company, open source project, news organization, or simply believe in the right to signal when you can no longer speak freely, this was built for you.</em></p><div><hr></div><p><strong>Alexander Weber</strong><br>Chief Aviculturist of Canaries</p><p><em>January 2026</em></p>]]></content:encoded></item><item><title><![CDATA[qop]]></title><description><![CDATA[A Standalone Rust Tool for Database Migrations]]></description><link>https://hexcode.substack.com/p/qop</link><guid isPermaLink="false">https://hexcode.substack.com/p/qop</guid><dc:creator><![CDATA[Alexander]]></dc:creator><pubDate>Wed, 13 Aug 2025 05:19:59 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3610bf02-ce93-4d54-8986-263de47ecdc9_1124x630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>qop</strong> (pronounced &#8220;kopp&#8221;) is a new command-line utility for managing database schema migrations, built entirely in Rust. I built it because I wanted something simple, standalone, and free from the baggage of an ORM. My frustration with ORM-bound migration tools led to <strong>qop</strong>&#8212;a tool that does one thing well: apply and roll back plain SQL migrations. In July 2025, it made <em>Crate of the Week</em> in the Rust community.</p><div><hr></div><h2>What makes qop different</h2><p>At its core, <strong>qop</strong> manages versioned database changes with minimal bloat. Each migration lives in a timestamped folder containing two files: <code>up.sql</code> for applying the change and <code>down.sql</code> for rolling it back. This format is widely used&#8212;think Diesel migrations&#8212;but <strong>qop</strong> is designed to work with any stack, not just a single ORM or framework.</p><p><strong>Highlights:</strong></p><ul><li><p><strong>Backend-agnostic</strong>: Works today with PostgreSQL and SQLite, with Cassandra support planned (among others).</p></li><li><p><strong>Simple migration structure</strong>: Timestamped directories with <code>up.sql</code>/<code>down.sql</code> ensure order and clarity.</p></li><li><p><strong>Lightweight CLI</strong>: One binary, no frameworks, no compile steps&#8212;just SQL.</p></li><li><p><strong>Built-in safety</strong>: Tracks applied migrations in a dedicated table, enforces linear history, and has recovery features for mismatched or missing files.</p></li></ul><p>Where other tools try to be all things to all people, <strong>qop</strong> keeps its scope tight. You write the SQL, it makes sure it runs in the right order&#8212;and that you can undo it safely.</p><div><hr></div><h2>Installation</h2><p>If you&#8217;ve got Rust installed, adding <strong>qop</strong> to your toolbox is a one-liner:</p><pre><code>cargo install qop</code></pre><p>If you&#8217;re working from the source repo:</p><pre><code>cargo install --path .</code></pre><p>That&#8217;s it. No extra dependencies beyond what your database already requires.</p><div><hr></div><h2>Initial Setup</h2><p>Run this in your project directory:</p><pre><code>qop init</code></pre><p>This creates a <code>qop.toml</code> config file, where you define your database connection(s). For example, Postgres might look like this:</p><pre><code>version = "&gt;=0.1.0"

[subsystem.postgres]
connection = { static = "postgresql://postgres:password@localhost:5432/mydb" }
schema = "public"
table = "migrations"
timeout = 30</code></pre><p>SQLite uses a file path instead of a schema:</p><pre><code>[subsystem.sqlite]
connection = { static = "sqlite:///path/to/database.db" }
table = "migrations"
timeout = 30</code></pre><p>Once configured, run:</p><pre><code>qop subsystem postgres init</code></pre><p>(or <code>sqlite</code> instead of <code>postgres</code>) to create the tracking table in your database.</p><div><hr></div><h2>Creating and Running Migrations</h2><p>To create a new migration:</p><pre><code>qop subsystem postgres new</code></pre><p>You&#8217;ll get a folder like:</p><pre><code>migrations/
&#9492;&#9472;&#9472; id=1691875567123/
    &#9500;&#9472;&#9472; up.sql
    &#9492;&#9472;&#9472; down.sql</code></pre><p>Edit <code>up.sql</code> with your schema change and <code>down.sql</code> with the rollback. Then apply:</p><pre><code>qop subsystem postgres up</code></pre><p>This runs all pending migrations. Add <code>--count &lt;N&gt;</code> to limit how many are applied.</p><p>Rolling back is just as easy:</p><pre><code>qop subsystem postgres down</code></pre><p>By default, it undoes the most recent migration. Add <code>--count</code> to roll back multiple steps, or <code>--remote</code> to use the original down script saved in the DB (handy if local files have changed).</p><p>To see what&#8217;s pending:</p><pre><code>qop subsystem postgres list</code></pre><p>And if you want a dry-run preview of changes, use the experimental diff feature:</p><pre><code>qop --experimental subsystem postgres diff</code></pre><div><hr></div><h2>Advanced Features</h2><p><strong>qop</strong> isn&#8217;t bloated, but it does have a few tricks for real-world workflows:</p><ul><li><p><strong>History sync</strong>: Creates local stubs for migrations found in the DB but missing locally.</p></li><li><p><strong>History fix</strong>: Reorders out-of-sequence migrations to maintain a strict linear history.</p></li><li><p><strong>Targeted apply/rollback</strong>: Apply or revert a single migration by ID without running others.</p></li><li><p><strong>Shell completions &amp; manual</strong>: Generate completions for Bash, Zsh, Fish, or export the full help as Markdown.</p></li></ul><div><hr></div><h2>How It Stacks Up</h2><ul><li><p><strong>Diesel CLI</strong>: Similar migration structure, but Diesel&#8217;s tied to its ORM and setup. <strong>qop</strong> is standalone, making it easier to drop into any project.</p></li><li><p><strong>Refinery</strong>: Offers library integration and more database support today. <strong>qop</strong> sticks to CLI-only and plain SQL for simplicity.</p></li><li><p><strong>SeaORM Migrations</strong>: Code-first migrations in Rust vs. <strong>qop</strong>&#8217;s SQL-first approach. If you prefer to write SQL directly, <strong>qop</strong> feels cleaner.</p></li><li><p><strong>Flyway/Liquibase</strong>: Broad database support and enterprise features&#8212;but heavier. <strong>qop</strong> feels more nimble for Rust developers who don&#8217;t need Java-based tooling.</p></li></ul><div><hr></div><h2>My final thoughts</h2><p><strong>qop</strong> was built as an alternative migration tool because none of the existing ones felt right for me. It&#8217;s not trying to be the centerpiece of your database stack&#8212;it&#8217;s a rusty wrench you reach for when you need migrations handled cleanly. If you&#8217;re done wrestling with ORM migration quirks, or just want a lightweight, no-surprises solution, give <strong>qop</strong> a look.</p><p>It&#8217;s still young (0.4.x as of August 2025), but already capable. And with its open-source MIT license and active development, you can use it for whatever you&#8217;d like.</p>]]></content:encoded></item><item><title><![CDATA[bobr - Command Multiplexer]]></title><description><![CDATA[Showcase of a simple, straight forward CLI app with no bullshit]]></description><link>https://hexcode.substack.com/p/bobr-command-multiplexer</link><guid isPermaLink="false">https://hexcode.substack.com/p/bobr-command-multiplexer</guid><dc:creator><![CDATA[Alexander]]></dc:creator><pubDate>Fri, 15 Nov 2024 18:05:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!XQEL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F512ead63-2508-4cb4-a839-ae33344c37dd_804x732.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>bobr</em> (polish word for &#8220;beaver&#8221;) is a simple command multiplexer for your terminal. Today I&#8217;m going to talk about some aspects of.</p><ul><li><p><a href="https://crates.io/crates/bobr">crates.io</a></p></li><li><p><a href="https://github.com/replicadse/bobr">GitHub repo</a></p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XQEL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F512ead63-2508-4cb4-a839-ae33344c37dd_804x732.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XQEL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F512ead63-2508-4cb4-a839-ae33344c37dd_804x732.png 424w, https://substackcdn.com/image/fetch/$s_!XQEL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F512ead63-2508-4cb4-a839-ae33344c37dd_804x732.png 848w, https://substackcdn.com/image/fetch/$s_!XQEL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F512ead63-2508-4cb4-a839-ae33344c37dd_804x732.png 1272w, https://substackcdn.com/image/fetch/$s_!XQEL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F512ead63-2508-4cb4-a839-ae33344c37dd_804x732.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XQEL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F512ead63-2508-4cb4-a839-ae33344c37dd_804x732.png" width="804" height="732" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/512ead63-2508-4cb4-a839-ae33344c37dd_804x732.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:732,&quot;width&quot;:804,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:133021,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!XQEL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F512ead63-2508-4cb4-a839-ae33344c37dd_804x732.png 424w, https://substackcdn.com/image/fetch/$s_!XQEL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F512ead63-2508-4cb4-a839-ae33344c37dd_804x732.png 848w, https://substackcdn.com/image/fetch/$s_!XQEL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F512ead63-2508-4cb4-a839-ae33344c37dd_804x732.png 1272w, https://substackcdn.com/image/fetch/$s_!XQEL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F512ead63-2508-4cb4-a839-ae33344c37dd_804x732.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4nh2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38f022ab-f4c9-463c-9f52-81053bcef043_516x131.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4nh2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38f022ab-f4c9-463c-9f52-81053bcef043_516x131.png 424w, https://substackcdn.com/image/fetch/$s_!4nh2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38f022ab-f4c9-463c-9f52-81053bcef043_516x131.png 848w, https://substackcdn.com/image/fetch/$s_!4nh2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38f022ab-f4c9-463c-9f52-81053bcef043_516x131.png 1272w, https://substackcdn.com/image/fetch/$s_!4nh2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38f022ab-f4c9-463c-9f52-81053bcef043_516x131.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4nh2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38f022ab-f4c9-463c-9f52-81053bcef043_516x131.png" width="516" height="131" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/38f022ab-f4c9-463c-9f52-81053bcef043_516x131.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:131,&quot;width&quot;:516,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:28721,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4nh2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38f022ab-f4c9-463c-9f52-81053bcef043_516x131.png 424w, https://substackcdn.com/image/fetch/$s_!4nh2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38f022ab-f4c9-463c-9f52-81053bcef043_516x131.png 848w, https://substackcdn.com/image/fetch/$s_!4nh2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38f022ab-f4c9-463c-9f52-81053bcef043_516x131.png 1272w, https://substackcdn.com/image/fetch/$s_!4nh2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38f022ab-f4c9-463c-9f52-81053bcef043_516x131.png 1456w" sizes="100vw"></picture><div></div></div></a></figure></div><h2>What does it do</h2><p>In a nutshell, <em>bobr</em> executes multiple commands in the terminal at the same time (as sub process). That&#8217;s it.</p><p>Whether you want to just speed things up by executing them in parallel, or run multiple long running tasks in parallel as shown above. The example above shows two long running tasks (starting a HTTP server as well as the frontend).</p><p><em>bobr</em> also forwards signals towards the processes started with it. That means that with a single &#8220;Ctrl+C&#8221; command (SIGINT), you can interrupt and abort all processes that run through <em>bobr</em>.</p><p>That&#8217;s it. No fancy magic or anything, just a straight forward simple quality of life tool that I built mainly because I was fed up with bash backend processes and bringing them back into the foreground etc.</p><h2>Implementation</h2><ul><li><p>Language: <a href="https://www.rust-lang.org/">Rust</a><br>My language of choice. Every day. All day. And night.</p></li><li><p>Arguments: <a href="https://crates.io/crates/clap">clap</a> (<a href="https://crates.io/crates/clap_complete">clap_complete</a>, <a href="https://crates.io/search?q=clap_mangen">clap_mangen</a>, <a href="https://crates.io/search?q=clap-markdown">clap-markdown</a>)<br>It&#8217;s quite a big combination of crates here but it gives you full posix compliant argument parsing, a great interface to work with and manpages + markdown manuals</p></li><li><p>Async runtime: <a href="https://crates.io/crates/tokio">tokio</a><br>tokio is the #1 async runtime library</p></li><li><p>Signal handlers: <a href="https://crates.io/crates/signal-hook">signal-hook</a><br>Makes it super simple to list for signals like SIGINT ot SIGTERM</p></li><li><p>Cross-thread message channels: <a href="https://crates.io/crates/flume">flume</a><br>Simple messages across thread boundaries with a mpsc architecture</p></li><li><p>Error handling: <a href="https://crates.io/crates/anyhow">anyhow</a> + <a href="https://crates.io/search?q=thiserror">thiserror</a><br>The easiest way to handle errors properly, without need for complex custom error types</p></li><li><p>Terminal manipulation: <a href="https://crates.io/crates/crossterm">crossterm</a><br>Great for manipulation terminal output incl. coloring, clearing screen, moving cursor and much more</p></li></ul><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://hexcode.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading hexcode software! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[About software quality]]></title><description><![CDATA[A few thoughts on software quality]]></description><link>https://hexcode.substack.com/p/about-software-quality</link><guid isPermaLink="false">https://hexcode.substack.com/p/about-software-quality</guid><dc:creator><![CDATA[Alexander]]></dc:creator><pubDate>Wed, 13 Nov 2024 23:03:40 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d8da56c7-f254-4a03-97b3-7da0fa01eb63_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Software quality is a fundamental aspect of any software product in existence. But what does it really mean? Is the term blurry or well defined?</p><p>Being able to identify (and measure) quality of any work product is always positive. It helps in delivering a product that suits the customers, lets you articulate the reason for software changes and is, in some cases, required to meet specific standards either by policy or law.</p><p>Today, we&#8217;re looking into what this term actually means.</p><h2>Dissecting Software Quality</h2><p>A broad term like software quality needs to be dissected into smaller, interpretable pieces in order to work with it effectively. For this, I would like to introduce <em>one way</em> of doing this using a well defined ISO standard.</p><p>Meet ISO 25010:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MBGg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F586f86e6-5fb4-4103-b6f6-7fd8dd81954e_1521x430.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MBGg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F586f86e6-5fb4-4103-b6f6-7fd8dd81954e_1521x430.png 424w, https://substackcdn.com/image/fetch/$s_!MBGg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F586f86e6-5fb4-4103-b6f6-7fd8dd81954e_1521x430.png 848w, https://substackcdn.com/image/fetch/$s_!MBGg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F586f86e6-5fb4-4103-b6f6-7fd8dd81954e_1521x430.png 1272w, https://substackcdn.com/image/fetch/$s_!MBGg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F586f86e6-5fb4-4103-b6f6-7fd8dd81954e_1521x430.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MBGg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F586f86e6-5fb4-4103-b6f6-7fd8dd81954e_1521x430.png" width="1456" height="412" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/586f86e6-5fb4-4103-b6f6-7fd8dd81954e_1521x430.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:412,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;ISO 25010&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="ISO 25010" title="ISO 25010" srcset="https://substackcdn.com/image/fetch/$s_!MBGg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F586f86e6-5fb4-4103-b6f6-7fd8dd81954e_1521x430.png 424w, https://substackcdn.com/image/fetch/$s_!MBGg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F586f86e6-5fb4-4103-b6f6-7fd8dd81954e_1521x430.png 848w, https://substackcdn.com/image/fetch/$s_!MBGg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F586f86e6-5fb4-4103-b6f6-7fd8dd81954e_1521x430.png 1272w, https://substackcdn.com/image/fetch/$s_!MBGg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F586f86e6-5fb4-4103-b6f6-7fd8dd81954e_1521x430.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Called &#8220;Software Product Quality&#8221;, this standard aims to dissecting software quality into many smaller, categorized sub-concerns.</p><p>Ranging from functional suitability through usability to portability, this standard covers many aspects that are relevant for <em>any</em> software.</p><p>As an introduction, here a quick overview about the given categories:</p><ul><li><p>Functional Suitability<br>aka does the program do what it is supposed to do</p></li><li><p>Performance efficiency<br>aka how does my program make use of the available resources</p></li><li><p>Compatibility<br>aka how nice is my program working together with other programs</p></li><li><p>Usability<br>aka how frustrated does my program make its users</p></li><li><p>Reliability<br>aka does my program do its thing when I want it to do it, and what happens if it doesnt</p></li><li><p>Security<br>aka security</p></li><li><p>Maintainability<br>aka how frustrated does my program make its developers</p></li><li><p>Portability<br>aka how tight is my program coupled to what it&#8217;s running on</p></li></ul><p>You will notice that Security does not really have a short form of definition since I think that all attempts to reduce it in size and scope can have severe implications and right now, let&#8217;s just roll with this term.</p><h2>Are all equally important?</h2><p>As so often, the answer is &#8220;it depends&#8221;. It depends on the environment. But luckily, we can induce an intrinsic ranking of some of the quality disciplines.</p><h3>Functional Correctness</h3><p>If the software does not do what it should do, all other aspects are irrelevant. Let&#8217;s say you&#8217;re writing a software with textbook code quality. Right abstractions, meaningful variables names, optimal time complexity etc. Only problem is that instead of a microservice for billing you built a CLI interface for Reddit.<br>It is indeed an extreme example but that should make the idea behind this very clear.</p><h3>Usability</h3><p>No customer, no money, no developer, no product, no company, no yacht. Customers are the fundamental concerns of every business (or at least should be) and it is everyones responsibility to champion their needs. In that sense, if your software is not usable by a customer, it wont be used and therefore it is waste.</p><h3>The rest</h3><p>I&#8217;ll cap it here as most other aspects can be mitigated. Some over-the-top examples to show what I mean:</p><ul><li><p>Software is running out of memory due to wasteful algorithms?<br>An economical solution could be to just add more memory (for now).</p></li><li><p>Software is not installable on a specific system?<br>Choose a different system it can be installed on.</p></li><li><p>Code quality is horrendous and unreadable?<br>You&#8217;ll have a high mean time to change but this is solvable with enough money and time.</p></li></ul><p>Obviously, all the things above are clearly extremes but this should transport the general idea behind it.</p><p>In any case, what you want to achieve is a balanced performance through any of the categories while keeping in mind that the optimal engineering solution is not always the most economical solution.</p><h3>Security</h3><p>I have not forgotten about this as it deserves it&#8217;s own little section. As for all of the aspects above, you want to achieve a base-line of quality in this section, too.</p><p>With regards to security, the implications can be severe though. Therefore, as economical as we want to be, it is our responsibility as engineers to make sure that minimum security requirements are met and monitored. This is an aspect that is sometimes valued as less important, especially by non-technical leadership, but it is a critical component of any software.</p><p>With that being said, I will encourage any engineers to stand their grounds against malpractice regarding security aspects. As a technical expert, no one is going to champion it if you don&#8217;t.</p><ul><li><p>Do not &#8220;worry about authentication later&#8221;<br>(actual statement I had to argue against more than only once)</p></li><li><p>Authorization is not &#8220;just an attribute&#8221;</p></li><li><p>Audit Logging is no automatical by-product<br>Consider properties like append-only and decentralization / copies. Who can access it? What is logged? &#8230;</p></li><li><p>MFA should not just be an &#8220;extra&#8221;</p></li><li><p>And please &#8230; start signing your Git commits.</p></li></ul><h2>Closing thoughts</h2><p>Software quality is often a quite blurry topic and this article does not even come close to defining it properly. It mainly is just a collection of my thoughts on the topic looking back at a 13 year career.</p><p>Code quality is not equal to software quality.</p><p>Customer obsession. It is one of the Leadership Principles at Amazon for a good reason. No customers, no company, no income. It is that simple.</p><p>I think in the end it is always important to look at software as a tool. Make your customers want to use your tool over the other ones. And when building the tool, we as developers sometimes have to stand our ground to adhere to minimum standards, making the tool safe to use, even if it costs more to produce.</p>]]></content:encoded></item><item><title><![CDATA[105 LeetCode questions in 10 days]]></title><description><![CDATA[I ran medium LeetCode questions - here are my thoughts]]></description><link>https://hexcode.substack.com/p/105-leetcode-questions-in-10-days</link><guid isPermaLink="false">https://hexcode.substack.com/p/105-leetcode-questions-in-10-days</guid><dc:creator><![CDATA[Alexander]]></dc:creator><pubDate>Wed, 13 Nov 2024 03:24:39 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/2acd2e9e-3bc6-45c9-a826-fa418e95b890_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>LeetCoding is undoubtedly the most important single skill for any software engineer. The reason for this is that it&#8217;s basically part of all interview processes out there. Do I necessarily agree with this practice? Maybe.</p><p>You probably don&#8217;t need to implement the <a href="https://en.wikipedia.org/wiki/Levenshtein_distance">Levenshtein distance</a> between two strings every day. Given that most of the engineering work is CRUD apps that follow a pre-established pattern anyway (especially in more junior ranks and bigger companies), the effectiveness of this interview practice might be debatable. Nevertheless, companies ask these questions for good reasons and it has shown that the process, as cumbersome as it may sometimes be, is working(*). Therefore, we have to face the reality of having to deal with these types of questions. I myself have been on both sides of the screen and want to share my thoughts about going back to the grind.</p><h2>TLDR;</h2><p>There is no magic formula or secret trick. In order to become better, nothing beats hard work. No one is going to do it for you.</p><p>After some time you will develop a sense / feeling for which classes of Algorithms are viable for which sets of problems. Trust your gut, it will mostly be right.</p><div><hr></div><h2>Timebox solutions</h2><p>Solving questions can be pretty hard at first, especially when not having been exposed to a variety of different problems and solution approaches.</p><p>When starting out, timebox yourself to 15-20 minutes for coming up with a plan / solution. Many problems have multiple solutions. When starting out, come up with a plan and verify that the solution is viable.</p><p>If it is, implement it and get it working. Now delete it and implement it again. Repeat this until you understand the approach.</p><h2>Keep notes</h2><p>I use Obsidian for my note taking. In there I have a folder called LeetCode. I keep notes for all questions I solve and label them with the difficulty and a &#8220;redo&#8221; flag.</p><p>For all problems, I store the problem statement, solution code and a description in which I explain the solution to myself. Don&#8217;t be lazy on that last part, this is the most important one.</p><p>Forcing yourself to explain the solution triggers the <a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging">rubber duck effect</a> and will help you understand and remember it better.</p><h2>Repetition</h2><p>On most days, I randomly pick 2-3 problems and just simply redo them. Practice is king and will go long ways.</p><p>With enough practice and learning / repeating the optimal solutions to problems, you have no choice but to better understand and remember them.</p><h2>Algorithms will repeat</h2><p>The more coding questions you solve, the more often you will remember something similar you&#8217;ve done somewhere else.</p><p>Most questions are solved using common techniques. This includes dfs and bfs for graph problems (most common asked interview questions), matrix (and array) traversal and manipulation, sorting and dynamic programming (dp).</p><p>For example, solve and compare LC-1 and LC-1010. Do you find similarities?</p><p>Many times, with practice, you can early identify what solution will be viable just by the feeling you develop for these kinds of problems. Trust your gut instinct and go with it.</p><p>I would confidently say that you can solve 80% - 90% of problems with common techniques once you identify what class this problem falls into.</p><h2>Dynamic Programming sucks</h2><p>Dynamic Programming problems are notorious for being just a pain. A lot of companies stopped asking these questions. Although I&#8217;d still encourage you to learn them, I found that the time required to do so is mostly better invested solving other problems. This, of course, only holds as long as you have relevant other questions on top of the pile.</p><p>If not, well&#8230; No one&#8217;s going to solve the problems for you.</p><h2>Buy Premium</h2><p>I know, it costs money. But trust me, just do it. Don&#8217;t doubt it, it&#8217;s worth it after all.</p><div><hr></div><p>Alex out</p>]]></content:encoded></item><item><title><![CDATA[Container <--> Host communication using named pipes]]></title><description><![CDATA[How to transmit data between a host and running containers using a practical example]]></description><link>https://hexcode.substack.com/p/container-host-communication-using-named-pipes</link><guid isPermaLink="false">https://hexcode.substack.com/p/container-host-communication-using-named-pipes</guid><dc:creator><![CDATA[Alexander]]></dc:creator><pubDate>Tue, 12 Nov 2024 00:08:54 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/7850b6e5-6677-49b5-a8c9-bad4e3b29266_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this post, we&#8217;re going to explore a way of transmitting data between a container and the host it&#8217;s running on, including an example implementation.</p><p>The application we&#8217;re building is going to send commands from a container to the host, having the host execute them. Obviously, this is a potentially critical operation that escapes the container isolation but it&#8217;s a great example for how to send any kind of messages towards the host (or back).</p><h2>Stack</h2><ul><li><p>NixOS<br>is a declarative Linux distribution that let&#8217;s us achieve reproducibility for our environment. It will be used as the host operating system.<br>We ignore windows, it does not exist.</p></li><li><p>podman / podman-compose<br>podman is a runtime for OCI containers much like docker. It supports rootless execution and otherwise is (almost) fully compliant to docker.</p></li><li><p>Rust<br>programming language of choice (just a small excerpt)</p></li></ul><h2>Host setup</h2><p>The following configuration is an excerpt for a NixOS host configuration that can be used to host our containers.</p><pre><code><code>{
  # ...

  # enable virtualisation and podman as backend
  virtualisation = {
    containers = { enable = true; };
    oci-containers = {
      backend = "podman";
    };
    podman = {
      enable = true;
      autoPrune = { enable = true; };
      dockerCompat = false;
      defaultNetwork = {
        settings = {
          dns_enabled = true;
        };
      };
    };
  };

  # make sure podman and podman-compose are installed
  environment = {
    # ...
    systemPackages = with pkgs; [
      # ...
      podman
      podman-compose
    ];

  # systemd services
  systemd = {
    services = {

      # startup service
      # ensures that there exists a named pipe we can use
      startup = {
        enable = true;
        wantedBy = [ "multi-user.target" ];
        
        serviceConfig = {
          Type = "oneshot";
        };

        script = ''
          mkdir /etc/example || true
          mkfifo /etc/example device/cmds || true
          chown -R example /etc/example
        '';
      };

      # listens on the named pipe, executing all commands sent through it using the eval command
      host_commands = {
        enable = true;
        wantedBy = [ "multi-user.target" "startup.target" ];

        serviceConfig = {
          Type = "simple";
        };

        script = ''
          while true; do eval "$(cat /etc/example/cmds)"; done
        '';
      };
    };
  };
}</code></code></pre><ul><li><p>systemd.services.startup<br>This is the service that bootstraps the named pipe at &#8220;<em>/etc/example/cmds</em>&#8220; if it does not exist.<br>This service spawns at system startup (&#8220;<em>multi-user.target</em>&#8221;).</p></li><li><p>systemd.services.host_commands<br>This is the service that continuously reads from the named pipe at &#8220;<em>/etc/example/cmds</em>&#8220; and executes the commands sent through it using the <em>eval</em> command.<br>This service spawns at system startup (&#8220;<em>multi-user.target</em>&#8221;).<br>Not that this service depends on the <em>startup</em> systemd service.</p></li></ul><h2>Agent software</h2><h3>Rust code</h3><p>The following code will write the command &#8220;<em>printf 'ping' &gt;&gt; /etc/example/container.log</em>&#8221; to the file &#8220;<em>/app/cmd</em>&#8221; every two seconds.<br>Not that in order to be able to write to the pipe, we only open the file using with write access as we won&#8217;t read from it and just use it as a sink.</p><pre><code>let mut fifo = std::fs::OpenOptions::new().write(true).open("/app/cmds")?;

loop {
  fifo.write_all(b"printf 'ping\n' &gt;&gt; /etc/example/container.log")?;
  fifo.flush()?;
  
  tokio::time::sleep(Duration::from_secs(60)).await;
}</code></pre><h3>compose.yaml</h3><p>In this yaml file, we build and run the agent software. The important part here is that we mount the file &#8220;<em>/etc/example/cmds</em>&#8220; into the container at path &#8220;<em>/app/cmds</em>&#8220;. This file is a named pipe in the host fs.</p><pre><code>services:
  agent:
    build:
      context: ../
      dockerfile: docker/Dockerfile
    container_name: agent
    restart: always
    volumes:
      - /etc/example/cmds:/app/cmds # this is the named pipe mount
    environment:
      DOTENV: "0"
      RUST_BACKTRACE: "1"</code></pre><h2>Result</h2><p>Running the container on the host system will use the file at &#8220;<em>/etc/example/cmds</em>&#8221; in order to exchange from the container to the host system. In our use-case, this data is shell commands that are then read and executed by the &#8220;<em>host_commands</em>&#8221; systemd service. In the given example, this results in the string &#8220;<em>ping\n</em>&#8221; being appended to the file at &#8220;<em>/etc/example/container.log</em>&#8221; every two seconds.</p><p>Disclaimer: Again, this is a pretty niche thing to do with containers and has obvious security implications but sometimes, use-cases like this can present themselves so why not. Don&#8217;t blame me if someone runs &#8220;rm&#8221; through that channel though.</p>]]></content:encoded></item><item><title><![CDATA[Talos Linux (single-node) on Hetzner Robot servers]]></title><description><![CDATA[How to create a simple single-node Kubernetes cluster on Hetzner Robot]]></description><link>https://hexcode.substack.com/p/talos-linux-single-node-on-hetzner</link><guid isPermaLink="false">https://hexcode.substack.com/p/talos-linux-single-node-on-hetzner</guid><dc:creator><![CDATA[Alexander]]></dc:creator><pubDate>Mon, 11 Nov 2024 21:36:09 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!4m3f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8aa64835-e2a0-4bfc-b124-179f5b4b3bda_800x365.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<ul><li><p><a href="https://github.com/siderolabs/talos">Siderolabs / Talos Linux OS</a></p></li><li><p><a href="https://robot.hetzner.com/">Hetzner Robot</a></p></li></ul><h2>Assumptions</h2><ul><li><p>Existing Hetzner account with access to Robot servers</p></li><li><p>Existing Robot server</p></li></ul><h2>Step 1: Rescue mode &amp; Talos&nbsp;install</h2><p>First, go to the Hetzner Robot console and go to the &#8220;Rescue&#8221; tab. Select and SSH key from your known keys and press &#8220;Activate Rescue System&#8221;.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4m3f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8aa64835-e2a0-4bfc-b124-179f5b4b3bda_800x365.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4m3f!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8aa64835-e2a0-4bfc-b124-179f5b4b3bda_800x365.png 424w, https://substackcdn.com/image/fetch/$s_!4m3f!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8aa64835-e2a0-4bfc-b124-179f5b4b3bda_800x365.png 848w, https://substackcdn.com/image/fetch/$s_!4m3f!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8aa64835-e2a0-4bfc-b124-179f5b4b3bda_800x365.png 1272w, https://substackcdn.com/image/fetch/$s_!4m3f!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8aa64835-e2a0-4bfc-b124-179f5b4b3bda_800x365.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4m3f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8aa64835-e2a0-4bfc-b124-179f5b4b3bda_800x365.png" width="800" height="365" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8aa64835-e2a0-4bfc-b124-179f5b4b3bda_800x365.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:365,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4m3f!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8aa64835-e2a0-4bfc-b124-179f5b4b3bda_800x365.png 424w, https://substackcdn.com/image/fetch/$s_!4m3f!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8aa64835-e2a0-4bfc-b124-179f5b4b3bda_800x365.png 848w, https://substackcdn.com/image/fetch/$s_!4m3f!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8aa64835-e2a0-4bfc-b124-179f5b4b3bda_800x365.png 1272w, https://substackcdn.com/image/fetch/$s_!4m3f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8aa64835-e2a0-4bfc-b124-179f5b4b3bda_800x365.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The server is now going to restart into the rescue OS, a specialized type of operating system that is used to rescue machines that are broken in some way. We are going to use this to override the filesystem of the machine with Talos Linux.</p><p>After a reboot, connect to the machine via SSH, using the &#8220;root&#8221; user. Now perform the following steps&#8202;&#8212;&#8202;you might need to update drive names. In this example, I had &#8220;nvme0n1&#8221; and &#8220;nvme1n1&#8221;.</p><pre><code># Disable raid
mdadm --stop /dev/md[0-4]

# Wipe disks
sfdisk --delete /dev/nvme[0-1]n1
wipefs -a -f /dev/nvme[0-1]n1

# Download raw talos fs
wget https://github.com/siderolabs/talos/releases/download/v1.7.0/metal-amd64.raw.xz -O /tmp/talos.xz
# Replace system with talos
xz -d -c /tmp/talos.xz | dd of=/dev/nvme0n1

sync
reboot</code></pre><p>This script will now reboot the machine with Talos Linux as operating system.</p><h3>Step 2: Talos bootstrapping</h3><pre><code>talosctl gen config my-cluster-name https://[INSERT IP HERE]:6443

# Edit the '/dev/sda' drive in the control-plane.yaml to '/dev/nvme0n1'
vi ./control-plane.yaml

talosctl apply-config -f controlplane.yaml -n [INSERT IP HERE] -e [INSERT IP HERE] --insecure
export TALOSCONFIG=./talosconfig
talosctl config endpoint [INSERT IP HERE]
talosctl config node [INSERT IP HERE]
talosctl dashboard
talosctl bootstrap --talosconfig=./talosconfig
talosctl kubeconfig ./kubeconfig

export KUBECONFIG=./kubeconfig

# Optional: Upgrade for ISCSI support (needed for Longhorn)
# --preserve is needed for single-node clusters
talosctl upgrade --image factory.talos.dev/installer/613e1592b2da41ae5e265e8789429f22e121aab91cb4deb6bc3c0b6262961245:v1.7.1 -m powercycle --preserve

# Allow scheduling on control-plane node for single node clusters:
kubectl taint nodes --all node-role.kubernetes.io/control-plane-</code></pre><h3>Disclaimer</h3><p>This tutorial is not meant to create production clusters. It is a bare minimal example of how to get started with an affordable, single-node Kubernetes cluster on a dedicated Hetzner machine. For production use-cases you will need HA for control-plane and workers, verifying checksums when downloading files, additional firewall configurations etc. etc.</p><p>Happy hacking.</p>]]></content:encoded></item><item><title><![CDATA[Systems Design: API Key Authentication]]></title><description><![CDATA[A few thoughts on how to design for API Keys]]></description><link>https://hexcode.substack.com/p/systems-design-api-key-authentication</link><guid isPermaLink="false">https://hexcode.substack.com/p/systems-design-api-key-authentication</guid><dc:creator><![CDATA[Alexander]]></dc:creator><pubDate>Mon, 11 Nov 2024 21:27:41 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c896ee6f-76f6-408f-b0d9-8ac372f9e051_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>API keys are a common authentication mechanism for web APIs. On the surface, it is a rather simple method of creating a unique key and assigning it to a consumer of the API in order to authenticate the caller reliably.</p><p>As a common problem in systems, especially ones that are built according to service oriented design principles, this problem has been solved many times and in just as many different ways.</p><p>This is a post about a reference implementation I came up with and successfully used in many projects. The shown solution has been scaled to millions of callers, been extended to individual needs and proved stand the trials of entire product lifetime cycles.</p><h2>General Concept</h2><p>API keys are created server-side and delivered to the API caller. The key is then transformed (see below) and stored in a secure data store, only accessible to the authority and verifier.</p><p>The stored data MUST allow referencing but MUST NOT allow reconstruction of the key itself. The secret part is and will only be known by the caller of the API and MUST NOT be known to the API authority after it has been transferred to the customer. It&#8217;s basically the same reason why a system should not know the plain password for a user account.</p><p>API requests MUST carry the key with each request. In a HTTP setting, you would usually carry the API Key in the request header field under <em>Authorization</em> <em>x-api-key</em> or some other field name. Consider that if used under the <em>Authorization</em> header, you might want to prefix the key with some identifier to distinguish it from JWTs that are carried in the same field. This is common practice for APIs supporting multiple authentication paradigms.</p><h3>Key creation</h3><ol><li><p>API authentication authority creates API key pair</p><ol><li><p>It creates a unique key ID</p></li><li><p>It creates a secret part</p></li></ol></li><li><p>Storage</p><ol><li><p>The secret part is transformed through the argon2 key derivation function. The parameters of this function should be appropriate.</p></li><li><p>The key is stored in the data store. Fields include the Key ID (plain text), argon2 secret hash and optional information like the tenant, billing account, expiry time etc.</p></li></ol></li><li><p>Delivery</p><ol><li><p>The key is concatenated to a single string using a delimiter character. This caracter is not part of either the ID or Secret generation alphabet and must be unique inside the concatenated key.</p></li><li><p>The concatenated key is transmitted to the caller.</p></li><li><p>The caller verifies the API key, setting it from pending to active.</p></li></ol></li></ol><h3>API calls</h3><ol><li><p>The caller initiates a call to the API with the key contained inside the call. For HTTP APIs, this would be some header field.</p></li><li><p>The callee takes the key from the request and splits it into ID + Secret.</p></li><li><p>The callee performs a lookup of the Key ID in the data store and receives the information stored alongside it.</p></li><li><p>The callee verifies the argon2 hash from the data store with the secret from the API request.</p></li><li><p>IF NOT successful, the callee denies the call as not authenticated / unauthorized.</p></li><li><p>IF successful, the callee scrubs the API key information from the request and forwards it to the handler that is responsible for executing the request.</p></li></ol><h3>Key rotation (optional)</h3><p>Key rotation is a critical security feature and SHOULD be implemented by any API that supports key authentication.</p><ol><li><p>Caller initiates key rotation with old key (A) via API call</p></li><li><p>see <em>Key creation</em></p><ol><li><p>Mark current key (B) as pending (can only be used finish rotation)</p></li><li><p>Also, mark B as successor to A</p></li></ol></li><li><p>Call rotation completion endpoint with new key</p><ol><li><p>Marks A as deprecated</p></li><li><p>Marks B as active</p></li></ol></li><li><p>Perform other API calls from here exclusively with B.</p></li></ol><h2>Structure</h2><p>The API Key structure is as follows:</p><pre><code><code>&lt;API_KEY_ID&gt;&lt;DELIMITER&gt;&lt;API_KEY_SECRET&gt;</code></code></pre><ol><li><p>API_KEY_ID<br>A unique identifier of the API key. This information is known to the API authority as well as the consumer. It can be used to perform service, debugging, tracing and other operations. It is not security critical.</p></li><li><p>DELIMITER<br>Some delimiter that&#8217;s not a part of the character sets of the KEY_ID and KEY_SECRET generation algorithms.</p></li><li><p>API_KEY_SECRET<br>The secret part of the API key. This is a security critical information and MUST NOT be logged, displayed, stored or forwarded in any way.</p></li></ol><p>The overall structure here is quite similar to HTTP basic auth, concatenating username and password in the request.</p><h2>Considerations</h2><h3>Middlewares and authority</h3><p>In any API implementation, it is probably a good idea to split the responsibility for the auth subsystem into a separate unit. This could look different depending on the setup. Two options are an entirely separate service that acts as authorizer on API routes (let&#8217;s say for HTTP APIs) or a combination of middleware (verification) and authority (sub-router routes with handlers) inside of the service for a more monolithical approach to SOA. Both have advantages and disadvantages.</p><p>What these have in common is that between the API Gateway and the actual request handler containing the business logic, the API key is verified and the request is approved. In most cases, you want to have some information from the API key available to the request handlers. This can be done by enhancing the downstream request with the information needed by the request handlers. For example, AWS API Gateway Authorizers allow modification of the HTTP request towards downstream handlers.</p><p>In any case, the original API Key MUST be scrubbed from the request as it contains the original plain-text secret. This information MUST NOT be accessible outside the secure enclave of the auth system.</p><h3>Storage</h3><p>The API Key MUST be stored in a secure data storage. The secret is and will only be known by the caller. It generally is to be considered the same criticality as user passwords.</p><p>In order to achieve the requirement of allowing lookup but disallowing reconstruction, we store the Key ID in plain text as primary key in the data store. The secret part goes into some trap-door algorithm that allows us verification but disallows reconstruction of the original value.</p><p>For storage, consider the following alternatives:</p><ol><li><p>Dynmao like</p><ol><li><p><a href="https://aws.amazon.com/dynamodb/">DynamoDB</a></p></li><li><p><a href="https://www.scylladb.com/">ScyllaDB</a></p></li></ol></li><li><p>Redis like</p><ol><li><p><a href="https://redis.io/">Redis</a></p></li><li><p><a href="https://docs.keydb.dev/">KeyDB</a></p></li></ol></li></ol><p>In any case, since the information must be read for each request, you want to consider a fast alternative that allows quick reads when having the Primary Key at hand.</p><p>DynamoDB for example gives a single-digit millisecond guarantee on a lookup with high entropy on the partition key (in our case that&#8217;s the Key ID).</p><p>An API key struct could look as follows (pseudo data structure):</p><pre><code>#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ApiKeySecret {
  Argon2(String),
}
impl ApiKeySecret {
  pub fn verify(&amp;self, secret: &amp;str) -&gt; Result&lt;()&gt; {
    todo!("implement");
  }
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct APIKey {
  pub id: String, // primary key
  pub secret_hash: ApiKeySecret, // key secret
  
  // use-case specific (optional) fields
  pub tenant: String,
  pub expires_at: Option&lt;DateTime&lt;Utc&gt;&gt;,
  // ...
}</code></pre><h3>Security concerns</h3><p>Incoming plain-text API keys MUST NOT be logged in their raw form as this would expose the secret in the logs. The secret MUST be omitted / replaced when doing so. Pro tip: In Rust, you can implement the Debug (&#8230;) trait to customize how a key is logged, avoiding accidental leaks here.</p><p>API key secrets, like passwords, SHOULD be rotated on a schedule. Contrary to passwords, regular schedules do not lead to predictable patterns in the key since the secret is generated server side. This SHOULD be done with a high entropy in the secret generation algorithm, avoiding predictable patterns (like auto-incrementing numbers&#8230;).</p><p>Since the callee authority itself does not know the plain secrets of the API key, we already mitigate a whole set of concerns about unauthorized access. Nevertheless, altered information in the API key tables can have other implications regarding billing, metering and other business cases.</p><p>Additional security can be achieved by alerting on key rotations, implementing intrusion detection mechanisms that alert on unusual behavior and significantly restricting developer access to the production system, especially considering the secure enclave containing the key information.</p><h3>Argon2 key derivation function</h3><p><a href="https://www.argon2.com/">Argon2</a> is a key derivation function that has shown to be resistant to <a href="https://www.password-hashing.net/">cracking attempts</a>.</p><p>A key aspect of the password hashes is that the parameters used are stored inside the hash. This is a great feature because it allows changing the algorithm in the code while still supporting older secrets being validated.</p><h2>API keys vs. JWT</h2><p>JWT stands for JSON Web Token. A JWT is a very common authentication mechanism between a frontend (interactive user interface) and a backend service (like a RESTful HTTP API). Although JWTs can be used for other situations, its most common occurrence is in this setting.</p><p>The great feature of JWTs is that it enables us to store information inside the token and verify the integrity and validity of this information server side. This avoids database lookups for each request. The token is created by minting it server side, enclosing it on every HTTP call and then verifying the checksum of the token during the call on the server side. This can be done in many ways, with signed cookies being the most prominent one for ordinary frontends.</p><p>Given that the information is stored in the token, there are a couple of downsides to this approach. For one, the information in the token generally not encrypted (so it&#8217;s readable to the user). If the token stores information about the internal system, like authorization claims, this can expose information to the public that could otherwise stay hidden. Although you should not perform security by obscurity, this does technically serve as an additional attack vector. Also, once minted, JWTs can not easily be revoked, making long lasting tokens (like refresh tokens) more critical due to their blast radius. Once a token has leaked, it can be used to mint new tokens with the previous ones still being valid. It&#8217;s a downward spiral from here.</p><p>Generally speaking, JWT is better suited for user authentication to a system while API keys are better suited for system to system authentication (exceptions apply).</p>]]></content:encoded></item></channel></rss>