<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>progrium.xyz: articles</title><link>https://progrium.xyz</link><atom:link href="https://progrium.xyz/articles/feed.xml" rel="self" type="application/rss+xml"></atom:link><description>Jeff Lindsay, aka progrium. Simple tools that make computers more programmable.</description><language>en</language><item><title>Proposals, Not Milestones</title><link>https://progrium.xyz/articles/2026/proposals</link><guid isPermaLink="true">https://progrium.xyz/articles/2026/proposals</guid><pubDate>Wed, 04 Nov 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>I've started a new primitive called <a href="https://github.com/progrium/hyper9">hyper9</a>, initially as an experiment to see how far I could push working with coding agents. It's ambitious and exciting, and right now it's in a pretty good place. Though, maybe halfway to a real v0.1 release. Getting to this point with it forced me to finally solve a problem I've had with projects like this for a long time.</p>
<p>For day-to-day work we have todos, milestones, and sprints, and those work fine when we're actively in that mode. But my open source projects with larger arcs, and ideas that need a lot of thought before I can build them (let alone anybody else), have always been missing something. It turns out I already knew what it was: a written proposal process, sometimes called <a href="https://martinfowler.com/bliki/ArchitectureDecisionRecord.html">ADRs</a>. They've been very effective working with coding agents, but also, having coding agents has finally made the process practical for me.</p>
<h2>The usual loop</h2>
<p>Working with coding agents, I've settled into a plan-then-implement rhythm. I'll write a rough sketch prompt, then have the LLM turn it into a PLAN that's sort of like a spec. I review it, modify it, and then tell the agent to implement it.</p>
<p>From there it usually drifts out of sync, because there's no formal process. Sometimes when things are roughly done, I'll have the agent convert the PLAN into an updated DESIGN or SPEC that lives with the code, so there's some kind of design documentation and history. Other times I don't even keep the PLAN around. I just make commits and iterate forward.</p>
<p>For most projects so far that's been okay. Many open source projects, and some of my bigger projects usually ask you to &quot;submit an issue before a PR&quot; so there's a chance to talk about the design first. That's a hint at what's missing: a place for the design to be discussed and decided before anyone writes code. Not just initially, but throughout development.</p>
<h2>How hyper9 started</h2>
<p>hyper9 began the way most of my projects do lately: chatting with ChatGPT on my phone, notebook in hand. I put the idea together in my head, did research, and validated assumptions. Eventually I said &quot;draft a design doc,&quot; and that became the working design document until implementation started.</p>
<p>The project was too big for a single plan, so the doc broke it into milestones. Each milestone was a collection of things. The early ones were small, but they got bigger and bigger. Though, you wouldn't notice it by looking at the words.</p>
<p>I should have seen the problem coming when I started doing &quot;milestone prep.&quot; Before taking on M3, I'd ask what needed to happen first. That turned into a planning step of its own, full of unlisted work: set up a test harness, do some prerequisite work, answer some open questions. Once that was done, I could usually turn the milestone over to the agent to implement on its own.</p>
<h2>Stuck in research mode</h2>
<p>As the project grew, the milestones I'd defined upfront grew too. Sometimes after prep and planning, the agent would just read files and look things up for 30 minutes. One time I left it for over two hours, and it still hadn't written anything. It was all reading source and researching, which does go into its context and makes for a better implementation.</p>
<p>It was funny, it was like a new junior programmer too afraid to start because they don't feel prepared. Safer to just keep learning.</p>
<p>The real issue was that the milestones were too big. They were one-liners like &quot;M4: this, this, this, and this. Should be able to X.&quot; Each of those &quot;this&quot; items probably deserved its own plan.</p>
<h2>Patching the process</h2>
<p>This time I did one thing right: I had the agent keep the overall plan, which had become DESIGN, up to date. Whenever planning or implementing a milestone surfaced something that would change or add to the design, the agent updated the doc. It also added a short entry to a Milestone Changelog section, recording what changed and why, grouped by milestone.</p>
<p>That worked, but then dependencies showed up. Dependencies were how the milestones got ordered in the first place, and new discoveries could change that order.</p>
<p>Then I started running work in parallel. A milestone plan would lay out steps, and if one could be done independently, I'd have another agent session take it. They usually touched different files, and occasional overlap was fine. But the DESIGN doc and its changelog became a shared hotspot that every session wanted to edit.</p>
<p>I originally had M0 through M10 milestones. I assumed the early ones would be the hardest, with all the setup and foundation work, and later ones would get easier as &quot;just features.&quot; The opposite happened. The project got bigger and more complex, and the later milestones listed a bunch of pretty big things.</p>
<p>So I stopped at M4. It was a good point for validation and demos that showed, yes, this will work. And then I had the agent help me come up with a new process.</p>
<h2>Enter proposals</h2>
<p>I've always wanted to run a project on a written proposal system. The idea goes back to internet protocols and RFCs (Requests for Comments). Python has PEPs (Python Enhancement Proposals), and Go has its own proposal process. You get a written record of how an idea evolved: its design and its plan for implementation. You can collect comments and revise before approving and building it. And it lines up neatly with the plan-then-implement loop you already want with coding agents.</p>
<p>So I based hyper9's new process on Go's, with some modifications. Fittingly, the first proposal was for the proposal system itself: <a href="https://github.com/progrium/hyper9/blob/main/design/proposals.md">proposals.md</a> explains the motivation, how it works, and how we migrated to it. The rules live in the <a href="https://github.com/progrium/hyper9/blob/main/design/README.md">design README</a>.</p>
<p>The short version:</p>
<ul>
<li><strong>No more milestones.</strong> Each piece of work is its own proposal, a Markdown file in <code>design/</code> covering design and planning.</li>
<li><strong>Dependencies in frontmatter.</strong> Each proposal lists what it <code>depends</code> on, along with status and other bookkeeping. The order of work comes from the dependency graph, not a numbered sequence.</li>
<li><strong>A clear workflow.</strong> A sketch becomes an initial proposal with open questions. Then there's room for discussion, a final comment period, and formal approval.</li>
<li><strong>A tool that knows what's ready.</strong> It reports when a proposal is accepted and all its dependencies are implemented, so it's ready to build. It also validates the graph in CI and regenerates a status table in the project README.</li>
<li><strong>GitHub issues for discussion.</strong> Filing a proposal opens an issue for open discussion and tracking. Revisions happen as commits to the doc.</li>
<li><strong>DESIGN stays current.</strong> DESIGN.md now describes only what exists. The PR that finishes a proposal updates it.</li>
<li><strong>A human signs off.</strong> Agents can help write proposals, but a named person has to direct, review, and sign the work.</li>
</ul>
<p>The old M0 through M4 became a single &quot;baseline&quot; proposal marked implemented. The remaining milestones were split by feature into separate drafts.</p>
<h2>Why it works</h2>
<p>This solves the pre-development design process. It documents the thinking and the original plan, and it keeps the main design doc up to date as a current spec rather than a pile of plans.</p>
<p>It also makes open contributions easier, both for design and for the question &quot;what's ready that I can help with?&quot; And that applies to agents too. I can point an agent at a proposal to flesh it out, challenge it, or at another to implement it. I can point several agents at different proposals without them getting in each other's way on design, prep, and planning. Actual implementation happens in branches when needed.</p>
<p>This setup makes agent assisted human contribution easy, too. You can do the same thing I do: point your coding agent at a proposal, keep a loose eye on what it does, and review what it completes. Though, if you didn't write the proposal, it's probably a good idea to talk through it with the agent to better understand it.</p>
<p>The best part is that the formalisms, the design and customization of this workflow, but more importantly the work of actually following it are suddenly not overwhelming. Agents carry most of that load. A process like this would have felt like bureaucracy for a small project before. Now it's mostly free.</p>
<h2>A real roadmap</h2>
<p>hyper9 now has a real roadmap: proposals at various stages, each with a <a href="https://github.com/progrium/hyper9/issues">GitHub issue</a>. Some are ready to start, and some are longer-term ideas still being developed. There are also a few unnumbered sketches in the <code>design/</code> directory that don't have issues yet. A table in the project README, kept up to date by a tool, makes it easy to see what's ready to work on.</p>
<p>I can think through a bunch of ideas, load them in as proposals, and let them simmer and develop until I think they're ready to be implemented. It's not quite a software factory, but it definitely feels like an augmented assembly line. One where feel in control of the design, and make better design decisions.</p>
]]></content:encoded></item><item><title>Turning VS Code into a Product Framework</title><link>https://progrium.xyz/articles/2025/vscode-as-product-framework</link><guid isPermaLink="true">https://progrium.xyz/articles/2025/vscode-as-product-framework</guid><pubDate>Mon, 30 Jun 2025 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Visual Studio Code is no longer just a developer tool. It’s now a highly malleable and embeddable product platform. Looking at it beyond a code editor, it's actually a general-purpose application shell with a familiar UI, plugin system, command framework, and cross-platform runtime. Recently it's been used as the platform for several major AI products, but more quietly it's also been used for much more, from cloud IDEs to domain-specific editors and internal dev tools.</p>
<p>In this series of posts, I’ll show how I’ve been turning VS Code into a reusable module; something you can embed, extend, and ship as part of your own product.</p>
<h2>Why VS Code as a Platform?</h2>
<p>I never thought the developer ecosystem would embrace a single editor as much as it has VS Code. Its dominance as a general-purpose coding environment is undeniable. This popularity and familiarity is one reason to build on it, but there are other attributes that make it a solid building block:</p>
<ul>
<li><strong>Generalized Design:</strong> It was designed not just as an editor, but as an integrated environment for various tools involved in the development workflow and beyond.</li>
<li><strong>Highly Extensible:</strong> Its extension API allows for deep customization, from minor modifications to total transformations, even into non-code oriented tooling.</li>
<li><strong>MIT License:</strong> The permissive MIT license means you can freely adapt and distribute it, provided you don't call it VS Code. The open source project is officially called &quot;Code - OSS&quot;</li>
</ul>
<p>Given its intended purpose is for software development, you might think this limits it to being a framework for devtools and code adjacent domains. Really, though, you can disable and rip all of that out and still have a great shell for applications that use the same general shape of its user interface. It might not be a fit for all products, but any application built around tabbed views, user-customizable sidebars, and is advanced enough to need a command palette would be a good candidate.</p>
<p>Even then, having integrated code editing abilities might be more of a benefit for non-development applications than you might think. Any professional knowledge tool these days needs some level of integration and extensibility. Imagine giving power users the ability to build these in-app. An embedded VS Code could just be used for this mode of your application. Imagine a game that comes with its own modding environment. Development environments are incredibly intricate systems to build, so having the most familiar of them that you can embed in your application is a huge win.</p>
<p>However, there is another argument that some kind of development environment might be worth including in your application, if not building on top of one directly.</p>
<h2>Embracing the Future of AI Agents</h2>
<p>Rather obnoxiously, AI is everywhere. For the foreseeable future, we are going to be working with more and more AI. But also for the foreseeable future, <strong>code will remain significantly cheaper to run than AI</strong>.</p>
<p>Luckily, AI is pretty good at writing code. To me, this points to an eventuality where most AI agents will be automating with code rather than direct control. This is great for a lot of reasons, which I can get into another time if people are interested. For now I just want to point out that any application or system that involves agentic AI will likely need a development environment and editor as a sort of &quot;admin backend&quot; for a behind-the-scenes code generating agent.</p>
<p>As of right now, the leading AI co-development environment, regardless of model or coding agent technology, is VS Code. So if you're interested in building agentic AI products or involving code generating agentic AI in your product, it might be worth thinking about how you can embed an environment like VS Code in your product.</p>
<h2>Maybe Avoid Forking VS Code</h2>
<p>Let's say you're in on using the VS Code open source project in your product. Your first thought might be to fork it, essentially adopting it wholesale. After all, that's what Cursor and Windsurf did! Well here are some quick stats that might be good to know about the VS Code project:</p>
<ul>
<li><strong>Massive Codebase:</strong> Currently it contains 1.3 million lines of TypeScript. This is comparable to the codebase of Grand Theft Auto III.</li>
<li><strong>Extensive Dependencies:</strong> Right now I count over 900 dependencies, which is a lot even for your average already-too-many-dependencies Node.js project.</li>
<li><strong>Bloated Project Size:</strong> After a full <code>npm install</code>, the project directory expands to a hefty 2.6GB. Not sure what all of that is, but almost 1 gig of that is <code>node_modules</code>.</li>
</ul>
<p>This is what you'd be inheriting and you haven't even started your application. On a high-end machine like my M3 Max, it takes around 30 minutes just to build the frontend. It takes 50 minutes on GitHub Actions. Why? I don't know, but maybe this is partly why they're porting the TypeScript compiler to Go.</p>
<p>Maybe you're just one person, like me, and this already seems overwhelming. Even for a small team, such a large and complex codebase and all those dependencies is rather prohibitive. Or maybe your team isn't interested in working with TypeScript, let alone 1.3 million lines of it. Or maybe, like me, you'd like to avoid Node.js and all those dependencies like the plague.</p>
<p>So is it already game over or is there an alternative approach? Well, since I'm also on this path as one developer with just a handful of GitHub sponsors funding this work, I had to find one. Because building an alternative to VS Code from scratch is also not an option for me.</p>
<h2>Towards Another Approach</h2>
<p>Thanks to VS Code's extensibility, there's a good chance the majority of what we'd want to change or add, we could do through extensions. This means changes to the codebase could at least be minimized to a few patches, making a full fork potentially unnecessary. We'd still need a build of VS Code we can configure and ideally embed in our application. It turns out there is a little known official option for this, but I'll have to explain some background on how it works.</p>
<p>VS Code is an <a href="https://www.electronjs.org/">Electron</a> app, which means it's mostly a web frontend that runs inside a webview in a native window. That's essentially a browser window without the chrome that just runs VS Code. What can't run in the webview is run in Node.js outside the webview. Electron provides a framework for this with cross-platform APIs to common native functionality like webview windows, menus, etc. The webview frontend talks to the Node.js backend via RPC and that's generally how most Electron apps work.</p>
<p>For VS Code, what runs outside the webview are subsystems for filesystem and shell access, the ability to run subprocesses for language servers, and extensions themselves that are typically run in a separate Node.js process. This will come up later.</p>
<p>Despite being made to allow an app to be built with web technologies, most Electron apps become quite coupled to Electron and Node.js. When people started building cloud IDEs where Electron didn't make sense and you could just view the frontend in your browser, they couldn't use VS Code because of this coupling. Eventually, thanks to projects like <a href="https://github.com/gitpod-io/openvscode-server">openvscode-server</a>, which patched VS Code to run as a normal web app in a Docker container, VS Code took many of these changes upstream and began to decouple VS Code from Electron.</p>
<h2>VS Code for the Web</h2>
<p>This decoupling led to <a href="https://code.visualstudio.com/docs/setup/vscode-web">VS Code for the Web</a>, a build target of VS Code that can run almost entirely in a webview or browser without the Node.js backend. Where openvscode-server decoupled it from Electron, VS Code for the Web even decoupled it from its own Node.js backend. In other words, it's a build of the VS Code editor that's just static files you can serve with any HTTP server.</p>
<p>When compressed into a zip file, <strong>the result is an 18MB artifact that could be deployed or embedded anywhere</strong>. In fact, there is a public deployment of VS Code for the Web at <a href="https://vscode.dev/">vscode.dev</a>, and even a semi-secret version deployed for GitHub. While logged in to GitHub, if you hit period (.) while on any repository, you're taken to that repository opened in an instance of VS Code for the Web. This is different from a GitHub Codespace, which runs a full backend and development environment in the cloud, similar to what you can do with openvscode-server. The reason it's different gets at the core limitations of VS Code for the Web:</p>
<ul>
<li>No shell or direct filesystem access</li>
<li>No extensions that use Node.js APIs</li>
</ul>
<p>It might be obvious but they both have to do with not having the Node.js backend. One of the major changes they made to support this build target was to make an extension host that runs in-browser using web workers, so extensions can only do what can be done in a browser web worker. Luckily, most of the core extensions have been ported to work in web workers.</p>
<p>Unfortunately, most third-party extensions are likely to use at least one Node.js module or NPM package. This hurts most for language extensions that typically use Node.js to run an LSP server subprocess. So if you need to support specific third-party extensions, you'll have to fork them and find a way to make them work. Better than forking all of VS Code?</p>
<p>The lack of shell and filesystem are pretty easy to fix because both can be provided by extensions. We can just have a kind of &quot;system extension&quot; that gets a filesystem and shell over WebSocket.</p>
<h2>Embed and Extend</h2>
<p>With VS Code for the Web we get an 18MB editor &quot;module&quot; in a single file that we can embed in our apps and then extend with extensions. While it comes with some limitations, I believe it opens up even more possibilities. We could:</p>
<ul>
<li>serve it from any CDN or static host for the cloud.</li>
<li>use it on the desktop through any webview wrapper like Electron, Tauri, etc.</li>
<li>serve it in an application of any language to access via the browser.</li>
</ul>
<p>It becomes trivial enough to embed as a built-in editor for whatever application you're building. And it's extensible and malleable enough to become any number of different non-code editors, like a DAW for audio, or a CAD tool, or a game engine editor.</p>
<p>In the following posts for this series, we'll build a cross-platform desktop code editor in Go using this approach. Effectively a clone of VS Code that you can use as a foundation for your own application, without all the baggage of building directly on a fork of the VS Code project.</p>
]]></content:encoded></item><item><title>The Spirit of Plan 9 on the Web</title><link>https://progrium.xyz/articles/2025/spirit-of-plan9-on-the-web</link><guid isPermaLink="true">https://progrium.xyz/articles/2025/spirit-of-plan9-on-the-web</guid><pubDate>Mon, 05 May 2025 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>If you go back to the <a href="https://www.slideshare.net/slideshow/web-hooks/263894">first talk ever given on webhooks,</a> it opens on the
command-line. Specifically the Unix shell, focusing on one of its defining
features: pipes. The idea was that pipes brought a new level of compositionality
to programs, and webhooks could bring a new level of compositionality to web
apps. Perhaps you could say I was trying to bring the spirit of Unix to the web.</p>
<p>With this <a href="https://github.com/tractordev/wanix/releases/tag/v0.3-preview">last release of Wanix,</a> I'm at it again. This time with the successor
to Unix, a little known operating system called <a href="https://en.wikipedia.org/wiki/Plan_9_from_Bell_Labs">Plan 9 from Bell Labs</a>.</p>
<p>Plan 9 has been on my mind for quite a while. In fact,
around the time of that first talk on webhooks, the team behind Unix and
Plan 9 was being re-assembled to create the Go programming language. I pretty
instantly fell in love with the Go worldview, which turns out to be an outgrowth
of the Unix and Plan 9 values of simplicity, pragmatism, economy, and ultimately
compositionality.</p>
<p>Like Unix, the Plan 9 environment is really made for programmers and system
operators. I'll leave a deeper explanation of what makes Plan 9 great for
another post, but I do get into it a bit in the demo video for Wanix:</p>
<p><a class="vid" href="https://www.youtube.com/watch?v=kGBeT8lwbo0" data-yt="kGBeT8lwbo0" aria-label="Play video"><span class="play" aria-hidden="true"></span><span class="vs">youtube · loads when you press play</span></a></p>
<p>While I wanted to incorporate Plan 9 ideas into Wanix from the beginning, it
wasn't until we rebuilt it from scratch with that intention that the magic
really starts to come through. That's what this preview release is about.</p>
<p>Wanix is a whole new beast now. It's no longer a singular computing environment
that runs in the browser. It's now a primitive for building environments in
general. The demo shows a shell environment, but this environment is not the
point. It's just a way to bootstrap Wanix so you can use and explore it
interactively.</p>
<p>The point of this preview release is to get this primitive out there. I have my
uses for Wanix, and I plan to share them with the final 0.3 release, but until
then I wanted to let it all percolate. Maybe inspire people to get creative
with their own use cases.</p>
<p>Here's a quick rundown of Wanix features in this release:</p>
<dl>
  <dt>Plan 9 inspired design</dt>
  <dd>With the original intention to enable exploring Plan 9 ideas on modern platforms, we've ended up with a radically simple architecture around per-process namespaces composed of file service capabilities using similar design patterns to those found in Plan 9.</dd>
  <dt>Single executable toolchain</dt>
  <dd>The <code>wanix</code> executable includes everything needed to produce Wanix environments.</dd>
  <dt>Filesystem is the only API</dt>
  <dd>The Wanix microkernel is now simply a VFS module with several built-in file services exposed via a standard filesystem API. This ends up making the module itself a file service.</dd>
  <dt>Built-in Linux shell</dt>
  <dd>Using the built-in file service primitives, Wanix can bootstrap a Linux-compatible shell based on Busybox. It comes with several helper commands for working with built-in file services.</dd>
  <dt>Tasks and namespaces</dt>
  <dd>The Wanix unit of compute is a task, which is equivalent and compatible with POSIX processes but allows for different execution strategies. Each task has its own "namespace," which is the customizable filesystem exposed to the task.</dd>
  <dt>Core file services</dt>
  <dd>Wanix includes two singleton file services: one to manage tasks (similar to procfs), and one to manage "capabilities" which are user allocated file services. Built-in capabilities include: tarfs, tmpfs, and loopback.</dd>
  <dt>Web file services</dt>
  <dd>With future non-browser deployments in mind, all web related file services are packaged in a web module, which is also built-in but not considered core. This module includes these work-in-progress file services:
    <ul>
  <li><strong>opfs</strong>: For working with the <a href="https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system">OPFS</a> browser storage API</li>
  <li><strong>dom</strong>: For inspecting and manipulating the DOM</li>
  <li><strong>worker</strong>: For managing <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">web workers</a></li>
  <li><strong>pickerfs</strong>: Capability wrapping the <code>window.showDirectoryPicker()</code> method (not available yet in Safari and Firefox)</li>
  <li><strong>ws</strong>: Capability for working with WebSocket connections</li>
<li><strong>sw</strong>: For configuring the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">service worker</a>, which is used by the system now to cache all resources needed to run Wanix allowing offline usage, as well as exposing virtual URLs to the root namespace.</li>
</ul>
</dd>
<p>Go programmers might also appreciate the filesystem toolkit we've been working on
since before Wanix. It builds on the <code>fs.FS</code> abstraction in the standard library
and gives you DSL-like utilities for defining virtual filesystems like the
file services in Wanix. More on that in a dedicated post as well.</p>
<p>So far, the feedback has been really positive. I appreciate everybody taking the
time to process it. There's still lots to do. Wanix is itself its own universe,
but it's just one layer of the Tractor project. After a little vacation I'll be
back to continue work on both fronts. As usual, I'd love help.</p>
<p>Speaking of help, shout-out to <a href="https://joel.franusic.com/">Joël Franusic</a> for
the help and support. And as usual, big thanks to my <a href="https://github.com/sponsors/progrium">GitHub sponsors</a> for
making this possible.</p>
]]></content:encoded></item><item><title>State of Tractor 2025</title><link>https://progrium.xyz/articles/2025/state-of-tractor-2025</link><guid isPermaLink="true">https://progrium.xyz/articles/2025/state-of-tractor-2025</guid><pubDate>Fri, 28 Mar 2025 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Hey everybody, welcome to 2025! Belated as it may be. For the past 5 years I've
been heads down on an epic journey to find new and better (read: simpler) ways
to build software. Three years ago I posted a <a href="https://www.youtube.com/watch?v=yKZ15O7zeHY">kind of thesis video</a> for this
work and gave the journey a name: Tractor. Today I want to share a progress report.</p>
<p>Tractor is not a single project, but a growing toolkit and developing philosophy
around building software. However, documenting the specifics of this has not been
an immediate priority. The vision is so big and abstract that just writing about it,
especially so early, seemed less efficient (and less fun) than just building.
Plus, for me it feels impossible to talk about new ideas without being able to
reference tangible examples and let people process them interactively.</p>
<p>This year seems to be a turning point. I have enough building blocks that they
are starting to come together in interesting ways. I've also collected a bunch of
bite sized developer tips along the way. For these reasons, I've decided to start
posting publicly more. Hold me to it!</p>
<h2>Process and Aims</h2>
<p>Like all of my work, I'm driven by an interactive process of idealized design,
speculative primitive disaggregation, and bottom-up exploration. There is an
evolving vision of how making software could be, which is reified into an imaginary
toolset and paradigm. This is then broken down into the fewest primitives needed
to build that toolset and paradigm. Each primitive is imagined and validated against
two criteria: how easily it enables the vision, and how many <em>other</em> possibilities
it enables.</p>
<p>Put succinctly: I'm collecting and building powerful software primitives and
design patterns that will enable the future of software and computing.</p>
<p>If people don't do this, nothing changes. In fact, by default, things get worse.
In this case, the &quot;future&quot; is a stand-in for &quot;something better&quot;, which I understand
is quite relative and is a good reason I should write some kind of manifesto. In
the meantime, here are a few ideas I'm aiming for:</p>
<ul>
<li>Minimize the number of steps to get to <em>any</em> software from 0</li>
<li>Remain polyglot, the answer is not one langugage</li>
<li>Align with emerging technologies that matter (ie CRDTs not blockchain)</li>
<li>Be a platform for many visions, the answer is not one vision</li>
<li>Close gap between builder and user of software with live malleability</li>
<li>Explore malleability through virtualization of development tools</li>
<li>Bridge worlds and simplify, the answer is not one perspective/paradigm</li>
<li>Start with plumbing, build bottom-up (after idealizing top-down)</li>
</ul>
<p>This is a long journey, but it should pay dividends for everyone.</p>
<h2>2024 in Review</h2>
<p>Last year we got a lot done. We had some important releases, solved some hard
problems, and started the process of getting feedback from peers on key technologies
now that they've been baking a few years.</p>
<h3>Wanix</h3>
<p>Wanix started around October 2023 as a standalone experiment that would be
<a href="https://www.youtube.com/watch?v=KJcd9IckJj8">fun to talk about</a>. The experiment showed promise, so I collaborated with a
friend to explore further. About this time last year, we put out a <a href="https://github.com/tractordev/wanix/releases/tag/v0.2">0.2 release</a>
and got to <a href="https://www.youtube.com/watch?v=cj8FvNM14T4">show it off at WASM I/O</a>.</p>
<p>Wanix started as an homage to early Unix, seeing what fewest elements are needed
to build a general purpose computing environment capable of self-hosting its own
development. Except this time, starting in the browser and building around
WebAssembly.</p>
<p>By the 0.2 release, Wanix was a good demo, but not terribly useful. It was also
somewhat impractical being a 60MB asset for the web, and not easy to get running
locally for development. However, a few things I think it validated:</p>
<ul>
<li>WebAssembly is exciting, but still quite early</li>
<li>A CRDT-based multiuser filesystem is compelling, even if &quot;slow&quot;</li>
<li>It's a lot of work recreating a Unixy userland</li>
</ul>
<p>I also came away convinced Plan 9 was onto something, but disappointed this
iteration of Wanix wasn't able to capture that magic. I was also a bit bummed
I wasn't able to get VSCode to run in Wanix, as it would have greatly improved
the UX of using Wanix as a development environment.</p>
<h3>DarwinKit</h3>
<p>DarwinKit is an ambitious yet boring project to make Apple APIs usable in Go.
After deciding Electron was not going to be the answer in Tractor for cross-platform
webview apps, I started a cross-platform library in Go focusing more on the
desktop APIs exposed by Electron. This meant working with Apple APIs, and at the
time there were no Go bindings.</p>
<p>The desktop API library only needed a few basic Apple APIs, but the challenge of making
high-quality, idiomatic bindings for <em>every</em> Apple API (one of the largest API
surface areas, rouhgly 1/4 the size of AWS) was compelling. It would have to be automated.
If we could do it for Apple, we could do it for any API, which would come in handy
in the future.</p>
<p>Back in 2023, I re-wrote the whole system, getting us an order of magnitude more coverage,
from maybe 150 symbols represented to over 30k symbols. Still far from total
coverage, but enough that it seemed possible someday. Not only was this a rewrite,
but a rename, so it was initially released as a preview to give time for people to
prepare for a new repository name and backwards incompatible API.</p>
<p>In July 2024, we finally released <a href="https://github.com/progrium/darwinkit/releases/tag/v0.5.0">DarwinKit 0.5</a>.
Other than Wanix, DarwinKit has been the only project since 2020
to find a regular trickle of organic discovery. This is somewhat annoying because
it's also at the bottom of one of the deepest Tractor yak shaves and does not
contribute much to the development of the system. Though I have found it useful in
developing techniques to generate these kinds of bindings, which I <a href="https://www.youtube.com/watch?v=wBzHOTOf0wU">documented in
this talk</a>.</p>
<h3>Manifold</h3>
<p>Manifold is one of the key technologies of the Tractor system, providing a sort
of universal structure to assemble software systems on. As such, it's been allowed
the most time to cook. It's been re-written at least 5 times by now and I went
into it knowing that would probably be necessary.</p>
<p>Last October it was finally demonstrated publicly at <a href="https://2025.splashcon.org/">SPLASH</a>
(previously known as OOPSLA). Unfortunately, trying to pack so much into a 20
minute talk probably left most people confused. I did get kudos from a
Smalltalker for making Go more like Smalltalk.</p>
<p>It was a one-day workshop packed with dense talks and demos. By my slot my brain
was nearly fried, so it was also not one of my best performances. The win, though,
was just getting it out there, and I've been able to have some good conversations
about it since.</p>
<h3>New Apptron</h3>
<p>If Apptron is a familiar name, be prepared to unlearn what it is. Going back to the
Electron-inspired desktop API, it was originally packaged up as a standalone primitive
that could be used a bunch of ways, from any language, and could even be used in
shell scripts. It originally had a name as boring as the technology was, Hostbridge,
but that name turned out to be trademarked. Brainstorming led to Apptron, which was
less descriptive, but definitely more cool, and even sounded like Electron.</p>
<p>With all these projects, I'm not one to push something more than there is signal for
it. The response to Apptron was lackluster. Maybe because it was sponsor-walled,
or maybe because people had an Electron API at home, but it doesn't matter that
much. I made a great component for Tractor and I knew these early foundational
projects would be hard sells, even just for attention.</p>
<p>Eventually the code for what was once called Apptron was rolled into a package
simply called &quot;desktop&quot;, freeing up Apptron to be used for something more
deserving of a cool name.</p>
<p>After the Wanix 0.2 release, I went hard on &quot;VSCode in the browser.&quot; After 9
months off and on, I finally had VSCode running in the browser with a full Linux
development environment that was actually usable. I'm not sure how many people
have achieved this. I might be one of the first. Yes, cloud IDEs are a thing, but
they run the Linux environment, usually Docker, in the cloud. They usually even run
the VSCode backend in the cloud.</p>
<p>I started to envision a possible product. Not quite &quot;Tractor Studio&quot; but definitely
something resembling it that could iterate towards it. I'll save specifics for later
but we started using the name Apptron for this. In fact, there is a &quot;tech demo&quot;
version online today at <a href="https://apptron.dev/">apptron.dev</a>. Check it out!</p>
<p>There's a pretty good story about this side quest that involves some of
these other projects in the next section. Hopefully I'll get to sharing it soon.</p>
<h3>Other projects</h3>
<p>Most of these aren't documented yet and will probably come up in future posts, but
here are some other project repositories and some forks that went up last year:</p>
<ul>
<li><a href="https://github.com/progrium/env86">https://github.com/progrium/env86</a></li>
<li><a href="https://github.com/progrium/httpfs">https://github.com/progrium/httpfs</a></li>
<li><a href="https://github.com/progrium/vscode-web">https://github.com/progrium/vscode-web</a></li>
<li><a href="https://github.com/progrium/go-vscode">https://github.com/progrium/go-vscode</a></li>
<li><a href="https://github.com/progrium/go-netstack">https://github.com/progrium/go-netstack</a></li>
<li><a href="https://github.com/progrium/sys-wasm">https://github.com/progrium/sys-wasm</a></li>
</ul>
<h2>2025 in Preview</h2>
<p>This year I plan to write more and share more publicly here on my blog. This
could even include some kind of manifesto or at least an updated thesis. However,
what I have most in my writing queue are just quick helpful bits. Patterns and
conventions I use in my projects. Simple DIY alternatives to using complex tools.
Shorter, more useful posts.</p>
<p>I'm also trying to be more social. More importantly, I'm trying to get back into
community building. I have my eye on doing in-person events again, but for now
I'm warming up with online events in the Discord or on Twitch. And if you have
any favorite conferences or in-person meetups, coworking groups, or anything like
that, please let me know! I'm mostly back in the SF bay area these days.</p>
<p>In terms of Tractor projects, there are a few important releases coming up. After
that my forward energy will probably go into the new Apptron. However, I'm very
open to collaborations, especially projects relevant to my roadmap. As an example:</p>
<h3>Taragen</h3>
<p>Besides Wanix, over the last few months I've been working with a friend on a
Revolutionary, Extraordinary ... static site generator. No really. Actually,
I'm pretty thrilled about it. It's minimalistic, but extremely capable. It's
written in Go but uses JSX for templates. I've already ported my website to it.</p>
<p>In fact, planning to blog more is what motivated me to finish it up. I'm that
unsatisfied with existing options. Finally, though! Something I'm making that
will finally be relevant to you. You have a static site, right?</p>
<h3>Wanix</h3>
<p>This is pretty big to me. Since the beginning of the year, we've been re-building
Wanix up from scratch. This time, focusing on the Plan 9 architecture, making it
more production ready, and all kinds of exciting things.</p>
<p>We've made a kind of stream series about it all called <a href="https://www.youtube.com/playlist?list=PLw1XoTpvjktiMPzIMPawJpJCUr4MCpsU2">Wanix From Scratch</a>. Every
week we'd do 1 or 2 Twitch streams about 2 hours long. Usually the first hour
going over everything that happened since the last one. Then the second hour we
try to get some work done. There will be 20 days or 20 videos after the final
DEMO DAY happening live on Twitch <a href="https://www.twitch.tv/progrium/schedule?segmentID=121a6dcb-78a1-4030-a41c-33b3c939c09d">this Wednesday the 2nd at 11am pacific</a>!</p>
<p>Then after that a preview release will be up. Loads to talk about, but I'll save
it for later. I'm very excited, even though it's still just plumbing and most
people won't see obvious use cases for it, I think something special is going on.</p>
<h2>How to Help</h2>
<p>You made it this far. Now you want to, what, stay informed? Join the community?
Spread the word? First, thanks for reading. Second, any kind of participation would
be huge.</p>
<p>More posts are coming, so get subscribed here if you want. As I mentioned, we're
doing more events and coworking in the Discord, so feel free to join us there too.</p>
<p>You can also participate on GitHub with any of the fine projects mentioned. Finally,
thanks to my <a href="https://github.com/sponsors/progrium">GitHub Sponsors</a> as usual,
and a big thanks to my friends that have been helping out more recently.</p>
]]></content:encoded></item><item><title>Treehouse Influences</title><link>https://progrium.xyz/articles/2023/treehouse-influences</link><guid isPermaLink="true">https://progrium.xyz/articles/2023/treehouse-influences</guid><pubDate>Thu, 27 Apr 2023 00:00:00 GMT</pubDate><description>A quick tour of some of the influences for the Treehouse frontend and where it fits in the ecosystem.</description><content:encoded><![CDATA[<p>Over the past few months, we've built the <a href="https://treehouse.sh">Treehouse frontend framework</a> into an elegant, quality outliner that's open source, extensible, and gives you control of your data. I’d like to share some of the design influences for the Treehouse frontend, which should give a sense of the unique direction Treehouse is going from here.</p>
<p>The biggest influences for Treehouse are Tana, Notion, and Obsidian. These three represent the state of the art of personal and collaborative information management, sometimes simplified as note-taking tools. However, &quot;note-taking tools&quot; sells them short as they go beyond note-taking and information management. For lack of a better descriptor, many consider them <a href="https://www.forthought.tools/">tools for thought</a>.</p>
<h2>From Notes to Tools for Thought</h2>
<p>For most, note-taking brings to mind simple apps like Apple Notes and Google Keep, or even just a text file editor. These work well for people because they're already there and focus on quick and easy plain text capture. We could call this casual note-taking.</p>
<p>Back in the 2000s, hosted and self-hosted wikis became popular for easy, collaborative web publishing and knowledge management. Like Wikipedia, they could be used to build out hyperlinked knowledge repositories. Many wiki-based tools focused on their use as personal notebooks, one of the most influential examples being <a href="https://tiddlywiki.com/">TiddlyWiki</a>. The simple versatility of the wiki laid the groundwork for what we call &quot;tools for thought&quot; today.</p>
<p><img src="https://treehouse.sh/photos/blog/tiddlywiki.png" alt="TiddlyWiki screenshot"></p>
<p>When <a href="https://www.notion.so/">Notion</a> appeared in the mid-2010s, it built on the idea of the wiki and introduced structured data management with flexible views that <em>effectively gave you integrated, customizable versions of other productivity tools</em>. Notion, Airtable, and others helped bring in the age of no-code and low-code tools, allowing knowledge workers and entrepreneurs to build their own &quot;apps&quot; or solutions to problems without traditionally building software. Notion brought it all together in a simple, user-friendly experience based around the core idea of wiki-like information management.</p>
<p><img src="https://treehouse.sh/photos/blog/notion.jpeg" alt="Notion screenshot"></p>
<p>Meanwhile, a separate paradigm of note-taking tools emerged, focusing on the nested, tree-like structure of the outline. Perhaps inspired by tools like <a href="https://www.omnigroup.com/omnioutliner/">OmniOutliner</a> and <a href="https://orgmode.org/">Org Mode</a> for Emacs of the 2000s, <a href="https://workflowy.com/">Workflowy</a> appeared in 2010 as a no-frills web-based outliner.</p>
<p><img src="https://treehouse.sh/photos/blog/workflowy.png" alt="Workflowy screenshot"></p>
<p><a href="https://obsidian.md/">Obsidian</a> arrived in 2020 and is a local app focusing on Markdown files stored on your filesystem. Obsidian has a large plugin ecosystem giving it a wide breadth of features, but it’s especially appealing to those that want to own their data. If you strip away the plugins, Obsidian is a pretty simple hyperlinked Markdown editor.</p>
<p><img src="https://treehouse.sh/photos/blog/obsidian.png" alt="Obsidian screenshot"></p>
<p>Most recently, a tool in early access called <a href="https://tana.inc/">Tana</a> caught my attention. Their key innovation is taking the linked outline model of Workflowy and introducing schemas for nodes, making them into structured data. This gives Tana the embedded database functionality of Notion and Airtable, a step towards bringing the two paradigms of note-taking software together towards powerful, malleable tools for thought.</p>
<p><img src="https://treehouse.sh/photos/blog/tana.webp" alt="Tana screenshot"></p>
<h2>How Treehouse Fits In</h2>
<p>By now there's no shortage of options in this space, both as SaaS and open source. Take a look at this growing <a href="https://noteapps.info/">encyclopedia of note-taking tools</a>. Like Notion and Tana, many of the apps listed are much more than note-taking tools. Some lean into the framing of &quot;collaborative documents&quot;, and some are just categorized more generally as &quot;productivity tools&quot;. Tana goes so far as to say &quot;the everything OS&quot;.</p>
<p>Note-taking is just the beginning. It's a tangible gateway for something more powerful inherent to computing. Ever since Engelbart's <a href="https://en.wikipedia.org/wiki/The_Mother_of_All_Demos">mother of all demos</a>, the computing revolution seems to start with powerful tools for thought, which are, at minimum, good note-taking tools.</p>
<p><img src="https://treehouse.sh/photos/blog/nls.jpeg" alt="Engelbart using NLS"></p>
<p>Treehouse is a frontend and starter kit for anybody else that wants to explore this space with us. We will release a standalone product based on it soon, but most of the user-facing development will be done in the open source Treehouse project.</p>
<p><img src="https://treehouse.sh/photos/hero-image.png" alt="Treehouse screenshot"></p>
<p>Today with Treehouse you can build your own Workflowy equivalent, but soon it will become more comparable to Tana and Notion with the open extensibility of Obsidian. That alone is pretty exciting to have in a minimal open source project, but I can't wait to show you what will come next.</p>
<h2>Coming Soon</h2>
<p>In the next post, I'll start getting technical and share details on the Treehouse project stack and architecture. If you can't wait, we do have <a href="https://treehouse.sh/docs/dev/">documentation</a> for you to check out.</p>
<p>Thanks for reading, and a big thanks to my <a href="https://github.com/sponsors/progrium">sponsors</a> for supporting this kind of open source work. Share your thoughts and favorite note-taking tools in the <a href="https://github.com/treehousedev/treehouse/discussions/95">discussion thread</a> for this post.</p>
]]></content:encoded></item><item><title>Welcome to Treehouse</title><link>https://progrium.xyz/articles/2023/welcome-to-treehouse</link><guid isPermaLink="true">https://progrium.xyz/articles/2023/welcome-to-treehouse</guid><pubDate>Thu, 23 Feb 2023 00:00:00 GMT</pubDate><description>Announcing Treehouse, an open source note-taking frontend and will evolve into something much more.</description><content:encoded><![CDATA[<p>I'm excited to announce the start of a new major project that I'll be sharing the journey of on this fancy new blog. The project is called <a href="https://treehouse.sh/">Treehouse</a>, which is starting as an open source note-taking frontend and will evolve into something much more.</p>
<p><img src="https://treehouse.sh/photos/hero-image.png" alt="Treehouse screenshot"></p>
<p>We're creating a simple, hackable kernel of a note-taking tool as a web frontend that can be extended and customized by developers. Then we'll use that frontend to build a new kind of note-taking product. Today we have an early preview of our first minimum viable prototype of the frontend, and we'll be finishing up this release in the open on GitHub.</p>
<p>The <a href="https://github.com/treehousedev/treehouse">open source project</a> is a functional app frontend that you can deploy and back in various ways. For developers, this &quot;bring your own backend&quot; approach makes Treehouse a great starting point to build your own note-taking tool, just as we intend to do. We also have pre-made backends for those who want to use it with little-to-no programming. Either way, you'll always own your data, and now the system presenting it to you as well.</p>
<p>In this post, I'm going to talk about our minimum viable prototype and the approach we're taking with Treehouse as an open source project.</p>
<h2>Out of the Box</h2>
<p>While inspired by powerful tools like <a href="https://www.notion.so/">Notion</a>, <a href="https://tana.inc/">Tana</a>, and <a href="https://obsidian.md/">Obsidian</a>, we wanted to boil our initial goal down to the essentials while still being usable enough to use ourselves. As we continue to plan our prototype release milestone, we'd love to hear what you think is essential for you to have in a tool and platform like this, but here is what to expect as a baseline.</p>
<h3>Outliner with Markdown Pages</h3>
<p>At the heart of Treehouse is a graph-like system I've been developing called Manifold. This will play a big part in the long-term extensibility of Treehouse, which I'll talk more about in the future. For now, this maps very cleanly to the outliner model popularized by Workflowy, Tana, and others.</p>
<p>However, I'm also a fan of Notion-style pages and Obsidian's commitment to working with plain Markdown files. The Manifold system allows us to make certain nodes into Markdown pages. This hybrid model gets us the best of both worlds with room for even more possibilities later on.</p>
<h3>Quick Add and Daily Notes</h3>
<p>Being able to quickly get thoughts and information into the system has been a key aspect of every good note-taking system. This can be exposed a number of ways from browser extensions to desktop shortcuts, but what makes any &quot;quick add&quot; functionality quick is not having to think about where it will be organized.</p>
<p>Luckily, &quot;daily notes&quot; has become such a common pattern that many recent tools have managed daily notes built-in. This typical gives you a &quot;today&quot; note that is automatically organized into a calendar-like structure. This conveniently becomes the perfect destination for &quot;quick add&quot; notes.</p>
<h3>Full-text Search</h3>
<p>Whether you are an obsessive organizer or are too busy to take the time, solid full-text search is basically a necessity. It's how we quickly get to our data.</p>
<p>Out of the box we have a full-text search that doesn't require a backend. However, our pluggable backend will allow you to power search in a number of ways, and also opens up the ability to search into external systems that are important to you.</p>
<h3>Built-in Backends</h3>
<p>&quot;Bring your own backend&quot; is a critical part of the design of this project, but Treehouse also needs to be usable by people who prefer not to <em>build</em> their own backend. Our prototype release is planned to include a handful of built-in backends to get started with.</p>
<p>Our preview demo is using a localStorage backend, which works for development and special use cases. Of course, we will have a simple local filesystem backend for desktop scenarios, similar to Obsidian.</p>
<p>However, we're most excited for the GitHub backend. This functions similarly to the local filesystem backend, but it would be versioned in a central repository on your GitHub account and be accessible on any online platform.</p>
<p>Backends will not only let you extend storage, search, and authentication, but other aspects in the future.</p>
<h2>Project Overview</h2>
<p>The Treehouse codebase and open source project is as much the product as its features. The entire user and developer experience is being designed around simplicity and human ergonomics.</p>
<p>The Treehouse frontend is built with web technologies intended for use across web, mobile, and desktop platforms. The project is written in TypeScript using <a href="https://deno.land/">Deno</a> as its JavaScript toolchain.</p>
<p>The JavaScript ecosystem is a mess, but Deno provides a forward-looking, self-contained toolchain that's easy to love. We actually have a zero Node.js policy and avoid NPM modules as much as possible.</p>
<p>With minimal dependencies and an ongoing effort to keep the codebase small, it will always be easy to understand the entire Treehouse system. This will be further supported with a focus on documentation, both API docs and guides.</p>
<p>We're using a permissive open source license, allowing you to use or change our code as you see fit. Contribution and participation is welcome, but so is simply consuming downstream.</p>
<p>The project is actively being developed but we keep a <a href="https://treehouse.sh/demo/">preview demo</a> running off the main branch that should always be in working order.</p>
<h2>Coming Soon</h2>
<p>I'm excited to give you a deeper look at the project stack in a future post. For now, keep an eye out for the next post digging into the influences for Treehouse. We'll also try to answer &quot;why another note-taking tool&quot;, and tease some long-term ideas for the future.</p>
<p>Lastly, I want to mention the project is being developed in the open, not just with the code on GitHub, but most of my work is streamed and archived on <a href="https://www.twitch.tv/progrium">Twitch</a>. Feel free to come and co-work with me.</p>
<p>Thanks for reading, and a big thanks to my <a href="https://github.com/sponsors/progrium/">sponsors</a> for supporting this kind of open source work.</p>
<p><em>Check out more at <a href="https://treehouse.sh">treehouse.sh</a> or <a href="http://eepurl.com/iluc5v">subscribe to our mailing list</a> for updates!</em></p>
]]></content:encoded></item><item><title>Cross-platform Native APIs Made Accessible with Apptron</title><link>https://progrium.xyz/articles/2022/cross-platform-native-apis-made-accessible-with-apptron</link><guid isPermaLink="true">https://progrium.xyz/articles/2022/cross-platform-native-apis-made-accessible-with-apptron</guid><pubDate>Fri, 01 Jul 2022 00:00:00 GMT</pubDate><description>Apptron is a software primitive that lets you drive native platform APIs using the technologies you’re already familiar with, enabling cross-platform webview apps and more.</description><content:encoded><![CDATA[<p>I’m happy to announce the first major building block of the Tractor System: Apptron.</p>
<p>Apptron is a software primitive that lets you drive native platform APIs using the technologies you’re already familiar with, enabling cross-platform webview apps and more.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ihqyuhbatzm3wq9luqt.png" alt="Apptron Demo Screenshot"></p>
<p>Usable from any language, or even the command line, Apptron gives you webview windows and common platform APIs for your simple scripts, homebrew utilities, or full applications. Building cross-platform programs that leverage native functionality (menus, dialogs, notifications, global shortcuts, etc) has never been more accessible.</p>
<p>These APIs are packed into a single executable that can be used a number of different ways, making Apptron a simple but powerful software primitive.</p>
<h4>Zero Config Native Apps from HTML</h4>
<p>Turn an HTML file and any other web assets into a self-contained, cross-platform executable in a single command.</p>
<h4>Language Agnostic Building Block</h4>
<p>Use the Apptron APIs like a library from any language without C extensions or tricky event loops.</p>
<h4>Shell Scriptable Native APIs</h4>
<p>Use platform APIs from CLI commands to break shell scripting out of the terminal and into the desktop GUI.</p>
<p>These features and more will be showcased in a series of demos coming out over the next few weeks. Given all the different ways it can be used, Apptron is something you really have to see in action. Catch their <a href="https://www.twitch.tv/progrium/schedule?segmentID=97869319-25d1-428f-bedb-4b372ca0db6a">premieres on Twitch</a>, or <a href="https://www.youtube.com/c/progrium">subscribe on YouTube</a> so you don't miss them.</p>
<h2>Early Access</h2>
<p>In the meantime, we're opening up Early Access via the Apptron website:</p>
<p><a href="https://tractor.dev/apptron">https://tractor.dev/apptron</a></p>
<p>By joining early access, you'll be invited to the currently private GitHub repository where you can try Apptron out, submit issues, and help bring Apptron to a stable 1.0 release and become public open source software.</p>
<h2>Help Wanted</h2>
<p>I've been using Apptron in some form with the rest of the Tractor System for over a year now, but to make this a solid standalone primitive I'll need your help. Please consider joining early access to play around with it and give feedback.</p>
<p>Anybody interested in contributing or helping drive this project should definitely request access and get involved. This is one of many projects I have in the pipeline and I can only do so much.</p>
<h2>Thanks</h2>
<p>A huge thanks to my <a href="https://github.com/sponsors/progrium">sponsors</a>, who I depend on to fund this early R&amp;D work on the Tractor System. As long as it's sustainable, I look forward to releasing more powerful open source primitives.</p>
<p>Excited to tell you more next week with the first Apptron demo!</p>
]]></content:encoded></item><item><title>Progrium Technology Thesis</title><link>https://progrium.xyz/articles/2022/progrium-technology-thesis</link><guid isPermaLink="true">https://progrium.xyz/articles/2022/progrium-technology-thesis</guid><pubDate>Tue, 15 Mar 2022 00:00:00 GMT</pubDate><description>How and why I'm building the "Photoshop of software" out of generative building blocks.</description><content:encoded><![CDATA[<p>Creating software is too complicated. Our options are overwhelming, our tooling is too bloated, and things rarely ever &quot;just work&quot;.</p>
<p>8 years ago, the late Joe Armstrong, co-creator of Erlang, gave one of his last talks at the 2014 Strange Loop conference. It was titled &quot;The Mess We're In&quot; and talked about the increasing over-complexity of software. &quot;Software entropy increases with time,&quot; he said, &quot;we need to reverse entropy.&quot; He said in his conclusion:</p>
<p>&quot;Computing was about controlling complexity, and we have failed miserably.&quot;</p>
<p>An echo of what Djikstra said in the <em>90s</em>: &quot;It is time to unmask the computing community as a Secret Society for the Creation and Preservation of Artificial Complexity.&quot;</p>
<p>Alan Kay, another computing pioneer, has a great word for this &quot;artificial complexity&quot;: complication.</p>
<p>&quot;The amount of complication [in software today] can be 100s, maybe 1000s of times more than the inherent complexity.&quot;</p>
<p>For 25 years, Kay has been giving talks saying, &quot;The computer revolution hasn't happened yet!&quot; I believe that this is true, in part, because complication has gotten in the way.</p>
<p>The risks with complexity, complication, and bloat are also only growing. It's 2022 and we’re still deploying software with logging libraries that let attackers run arbitrary code. We barely understand our own software systems, let alone the tools we use to make them. And yet, underneath this mess is probably the most powerful and constructive tool humanity has ever seen.<br>
 
Building software today <em>could</em> be as fast and intuitive as we made desktop publishing by the 1990s. Suddenly designers could sit with a client in front of the computer and just execute an idea, making iterations together in real-time. Watching the designer use these intuitive tools, the client might think, &quot;Oh, I could do that&quot;.</p>
<p>To get there with building software we need to reduce the complexity, and especially the complications, of making modern software. It's not just about user friendliness or making the process more visual. And it’s not about adding more. If anything, it’s about revisiting the idea of doing more with less.</p>
<p>Recently, tools falling under the label of &quot;nocode&quot; have shown the demand for non-programmers to be able to just make what they need, with &quot;no code&quot;. It does sort of hint at what a photoshop of software might be. But every nocode tool is a facade, another layer of complication. They exist for a particular market, offered as a service, with no path to a more general means of making software.</p>
<p>These tools have their place, the same way image macro generators have their place when Photoshop and Canva exist. I think if “nocode” tools could really save us, you'd be able to build a nocode tool with itself. They lack the true generativity of programming.</p>
<p>That said, I am all for <em>anything</em> that can be used as a building block in a software system. Whether it’s a library or a web API or a tool repurposed because you like its workflow. The less you have to build yourself, the better.</p>
<p>My experience has shown me time and again that there is a powerful strategy or guiding principle in what I call &quot;generative building blocks.&quot;</p>
<p>This philosophy applies to more than libraries and web APIs. I design everything as a building block, and everything out of building blocks. This is about more than just modularity, it's about <em>high-leverage generativity</em>.<br>
 
I've worked on a number of projects and technologies trying to bring out this particular generative building block approach and there are a couple of standout hits that I'll use as examples.</p>
<p>In 2006 I was working on a hosted source control web app (like GitHub) trying to expose repository hooks. These are shell scripts triggered by your local source control tool that can be used to customize and extend your workflow, almost like registering a callback for an event. Running user code in a web app, especially shell scripts, was not safe or practical. However, imagining if somehow it was, a flood of possibilities hit me. If only web apps could have extension points! Without over formalizing it, I called this &quot;webhooks&quot; and started giving talks about what we could have if we added webhooks to our apps. Throwing a PHP script online was an easy and common activity for web developers then, so functioning as &quot;hook&quot; script with a URL, you just needed to have web apps let you register it and then hit it on certain events.</p>
<p>Webhooks did end up making web apps more extensible and integratable, increasing their value and potential as building blocks. It also allowed for new kinds of API building blocks to emerge like Twilio, where the initial core product was built around webhooks to integrate with telephony and SMS. Webhooks also started creating a demand for what would later manifest as AWS Lambda for event handling, which quickly inspired &quot;serverless&quot; architecture. The concept of functions as a service was something I was pioneering in 2009 because of webhooks.</p>
<p>Later I found myself helping to prototype and conceptualize Docker, which turned software into a kind of standard unit, hence the shipping container metaphor. Although many interpreted it as a lightweight VM to throw whole systems into, I was in love with microservices designed to be run in tiny containers, configured by APIs instead of files, acting more like a little appliance than traditionally managed software. More like a simple building block.</p>
<p>Docker itself became a building block, opening up a whole new generation of platforms and developer tooling. One of my initial use cases for it was as a primitive to create a simple, hackable version of Heroku. Sure enough, using Docker and a few other components I made, I was able to &quot;throw together&quot; a simple Docker-powered Heroku alternative in 90 lines of Bash. This was called Dokku.</p>
<p>Dokku was a bit of a revelation. An expensive, top-tier platform valued in the hundreds of millions of dollars recreated with the right primitives in a weekend as a free Bash script...<br>
 
I want to have the primitives to create more software like Dokku--extremely simple, hackable alternatives to the world's best software. The resulting software isn't really the important part, though. The resulting building blocks are. Why use Figma if you could somewhat easily throw together your ideal figma?</p>
<p>Building simpler alternatives by disaggregating into new primitives expands the adjacent possibility space of what can be thrown together, while also simplifying and commoditizing what it takes to build great software. It actually is a sort of reversing of entropy! Repacking down our abstractions, engineering for generativity.</p>
<p>What I love is how &quot;building blocks&quot; brings to mind a sort of ideal vision we have of building software. Like LEGO, we snap together little components to realize something new. But this metaphor also points out how far that ideal is from reality. LEGO is fun. Building software really <em>isn't</em> most of the time. And I think trying to express the reality through this metaphor is helpful:</p>
<p>While ALL our software is made of many small bricks or components or modules, most come to us fully assembled with the bricks glued in place and painted over. We can't change it or even get a sense of how it was composed. Most apps come as an opaque &quot;turn-key solution&quot; to a specific problem because... that’s how you sell things.</p>
<p>Perhaps there’s nothing wrong with that, but we can at least try to find ways to build more software like LEGO kits, where the focus is on good, re-usable, re-combinable building blocks and showcasing what you can do with them. In the long term, I believe this is the way that provides the most value.<br>
 
A few years ago I realized that I need to build a single cohesive system that lets me express all these ideas, and really show the potential I've been talking about. I need to create my own LEGO system for software that would be an umbrella for all my work and let me build the way I want to build, while being at the very least a useful example to others...</p>
<p>So I'm announcing the Tractor System. It's not one project, but a system of projects building towards a malleable software environment and ecosystem. It is my take on a &quot;Photoshop of software&quot;.</p>
<p>Tractor is a system of building blocks and pragmatic ways of building centered around simplicity and generativity. It’s not a new language, it helps glue languages together. It’s not just an app builder, it’s a systems playground. Much of the tool is made with itself, out of the same primitives you use in it. It's designed to be as simple and understandable as it can be, top to bottom, inside and out.</p>
<p>The aim is to blur the line between user and developer. The computer is a computer because it can be <em>programmed</em>. The rushed, over-productization of shrink-wrapped software, pandering to enterprise organizations and the computer illiterate, has created so much complication it would seem we NEED big companies to build our software. But we don't. I'm constantly surprised how simple things really are or can be. With Tractor, eventually we'll just have operators: people that can use the computer in its full potential to solve problems, create systems, play, learn, work, and have fun.<br>
 
Some say we live in a post PC era, but the original idea of the PC, the <em>personal</em> computer, is and will always be significant because it puts a magical form of <em>leverage</em> into the hands of <em>individuals</em> for their own use. But instead of this becoming more obvious with time, this generative power has been centralized and sterilized into &quot;pop computing&quot;. Today we consume and are defined by software that benefits engines of capitalism more than engines of equity. You could say, instead of being able to program our own leverage, we are being programmed by those that can.</p>
<p>Luckily, underneath all the apps, social media, and enterprise &quot;solutions&quot;, there is still an <em>imagination compiler</em>. This is what I focus on. This is what I'd love to dust off, polish up, and see where that takes us in the proper hands.</p>
<p>Even if that doesn't happen, I'd love to just get some better building blocks. Perhaps just find simpler, more enjoyable ways to build software.<br>
 
As an independent creator, I depend on people like you to help fund this work, and I'd love you to come along and help make the Tractor System a reality by <a href="https://github.com/sponsors/progrium">becoming a sponsor</a>.</p>
<hr>
<p><em>Originally posted on <a href="https://progrium.com/blog/progrium-technology-thesis/">progrium.com</a> and <a href="https://www.youtube.com/watch?v=yKZ15O7zeHY">YouTube</a></em></p>
]]></content:encoded></item><item><title>Making programs interact using qtalk</title><link>https://progrium.xyz/articles/2021/making-programs-interact-using-qtalk</link><guid isPermaLink="true">https://progrium.xyz/articles/2021/making-programs-interact-using-qtalk</guid><pubDate>Tue, 24 Aug 2021 00:00:00 GMT</pubDate><description>qtalk-go is a versatile IPC/RPC library for Go</description><content:encoded><![CDATA[<p>Today I'm releasing a beta of <a href="https://github.com/progrium/qtalk-go">qtalk-go</a>, a versatile IPC/RPC library for Go. I've been using and iterating on it for 5 years to get it as simple and clear as possible.</p>
<pre tabindex="0" class="chroma"><code class="language-go"><span class="line"><span class="cl"><span class="c1">// client.go
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;context&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;log&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/progrium/qtalk-go/codec&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/progrium/qtalk-go/fn&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/progrium/qtalk-go/talk&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">ctx</span> <span class="o">:=</span> <span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// use talk.Dial to get a client
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">client</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">talk</span><span class="p">.</span><span class="nf">Dial</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span> <span class="s">&#34;localhost:9999&#34;</span><span class="p">,</span> <span class="nx">codec</span><span class="p">.</span><span class="nx">JSONCodec</span><span class="p">{})</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">defer</span> <span class="nx">client</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// call Upper and print the string return value
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="kd">var</span> <span class="nx">ret</span> <span class="kt">string</span>
</span></span><span class="line"><span class="cl">	<span class="nx">_</span><span class="p">,</span> <span class="nx">err</span> <span class="p">=</span> <span class="nx">client</span><span class="p">.</span><span class="nf">Call</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="s">&#34;Upper&#34;</span><span class="p">,</span> <span class="nx">fn</span><span class="p">.</span><span class="nx">Args</span><span class="p">{</span><span class="s">&#34;hello world&#34;</span><span class="p">},</span> <span class="o">&amp;</span><span class="nx">ret</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">ret</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// call Error and expect err to be the returned error
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">_</span><span class="p">,</span> <span class="nx">err</span> <span class="p">=</span> <span class="nx">client</span><span class="p">.</span><span class="nf">Call</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="s">&#34;Error&#34;</span><span class="p">,</span> <span class="nx">fn</span><span class="p">.</span><span class="nx">Args</span><span class="p">{</span><span class="s">&#34;user error&#34;</span><span class="p">},</span> <span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// Output:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// HELLO WORLD
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// remote: user error [/Error]
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span></code></pre><p>qtalk is based on over a decade of building and cataloging approaches, patterns, anti-patterns, and best practices in network programming. My interest goes all the way back to high school when I first started playing with Winsock attempting to make massively multiplayer games. Then as a young web developer, pushing the limits of HTTP, discovering how to stream real-time to the browser years before Websocket was dreamed up. Then further abusing HTTP to model other protocols like DNS and IMAP. I pioneered distributed callbacks with webhooks, which got me working at early Twilio where I started going deep on scalable, highly-available messaging architectures. This led me into distributed systems: discovery, coordination, scheduling, etc. I've seen a lot.</p>
<p>I originally wanted to release qtalk with a paper describing all the significant choices to consider when building a stack like this: message framing, data formats, transports, security mechanisms, protocol flows, queuing, multiplexing, batching, layering, schemas, IDLs, symmetrical vs asymmetrical, stateful vs stateless, TCP vs UDP, etc. It would be a sort of guide for building your own stack. I'd still like to write that at some point, but this post will have to suffice for now.</p>
<p><strong>qtalk makes no significant claims other than being the most bang for buck in simplicity and versatility.</strong> I've made a full walkthrough of various ways it can be used on the <a href="https://github.com/progrium/qtalk-go/wiki/Examples">wiki</a>, but I'll share a taste here.</p>
<p>Here is the server to the client code from above for you to try. Together they show qtalk being used in the simplest case, traditional RPC:</p>
<pre tabindex="0" class="chroma"><code class="language-go"><span class="line"><span class="cl"><span class="c1">// server.go
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;fmt&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;log&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;net&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;strings&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/progrium/qtalk-go/codec&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/progrium/qtalk-go/fn&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/progrium/qtalk-go/rpc&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">service</span> <span class="kd">struct</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">svc</span> <span class="o">*</span><span class="nx">service</span><span class="p">)</span> <span class="nf">Upper</span><span class="p">(</span><span class="nx">s</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">string</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">strings</span><span class="p">.</span><span class="nf">ToUpper</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// methods can opt-in to receive the call as last argument.
</span></span></span><span class="line"><span class="cl"><span class="c1">// also, errors can be returned to be received as remote errors.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">svc</span> <span class="o">*</span><span class="nx">service</span><span class="p">)</span> <span class="nf">Error</span><span class="p">(</span><span class="nx">s</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">c</span> <span class="o">*</span><span class="nx">rpc</span><span class="p">.</span><span class="nx">Call</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;%s [%s]&#34;</span><span class="p">,</span> <span class="nx">s</span><span class="p">,</span> <span class="nx">c</span><span class="p">.</span><span class="nx">Selector</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="c1">// create a tcp listener
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">l</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">net</span><span class="p">.</span><span class="nf">Listen</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span> <span class="s">&#34;localhost:9999&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// setup a server using fn.HandlerFrom to
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// handle methods from the service type
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">srv</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="nx">rpc</span><span class="p">.</span><span class="nx">Server</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Codec</span><span class="p">:</span>   <span class="nx">codec</span><span class="p">.</span><span class="nx">JSONCodec</span><span class="p">{},</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Handler</span><span class="p">:</span> <span class="nx">fn</span><span class="p">.</span><span class="nf">HandlerFrom</span><span class="p">(</span><span class="nb">new</span><span class="p">(</span><span class="nx">service</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// serve until the listener closes
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">srv</span><span class="p">.</span><span class="nf">Serve</span><span class="p">(</span><span class="nx">l</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span></code></pre><h2>Features</h2>
<p>Some basic features of qtalk-go are:</p>
<ul>
<li>heavily <code>net/http</code> inspired API</li>
<li>pluggable format codecs</li>
<li>optional reflection handlers for funcs and methods</li>
<li>works over any <code>io.ReadWriteCloser</code>, including STDIO</li>
<li>easily portable to other languages</li>
</ul>
<p>The more unique features of qtalk-go I want to talk about are:</p>
<ul>
<li>connection multiplexing</li>
<li>bidirectional calling</li>
</ul>
<h2>Multiplexing Layer</h2>
<p>The connection multiplexing layer is based on <a href="https://github.com/progrium/qmux">qmux</a>, a subset of SSH that I've <a href="https://dev.to/progrium/the-history-and-future-of-socket-level-multiplexing-1d5n">written about previously</a>. It was designed to optionally be swapped out with QUIC as needed. Either way, everything in qtalk happens over flow-controlled channels, which can be used like embedded TCP streams. Whatever you do with qtalk, you can also tunnel other connections and protocols on the same connection.</p>
<p>RPC is just a layer on top, where each call gets its own channel. This makes request/reply correlation simple, streaming call input/output easy, and lets you hijack the call channel to do something else without interrupting other calls. You can start with an RPC call and then let it become a full-duplex bytestream pipe. Imagine a call that provisions a database and then becomes a client connection to it.</p>
<h2>Bidirectional Calling</h2>
<p>Bidirectional calling allows both the client and server to make and respond to calls. Decoupling the caller and responder roles from the connection topology lets you implement patterns like the worker pattern, where a worker connects to a coordinator and responds to its calls.</p>
<p>This also allows for various forms of callbacks in either direction. Not only do callbacks let you build more extensible services, but generally open up more ways for processes to talk to each other. Especially when combined with the other aspects of qtalk.</p>
<p>Imagine a TCP proxy with an API letting services register a callback whenever a connection comes through, and the callback includes a tee of the client bytestream letting this external service monitor and maybe close the connection when it sees something it doesn't like.</p>
<h2>State Synchronization</h2>
<p>State synchronization isn't a feature but a common pattern you can easily implement in a number of ways with qtalk. While many people think about pubsub with messaging, which you can also implement with qtalk, I've learned you usually actually want state synchronization instead. Below is a simple example.</p>
<p>Our server will have a list of usernames connected, which is our state. When a client connects, it calls Join to add its username to the list. This also registers the client to receive a callback passing the list of usernames whenever it changes. The client can then call Leave, or if it disconnects abruptly it will be unregistered with the next update.</p>
<pre tabindex="0" class="chroma"><code class="language-go"><span class="line"><span class="cl"><span class="c1">// server.go
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;context&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;log&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;net&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;sync&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/progrium/qtalk-go/codec&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/progrium/qtalk-go/fn&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/progrium/qtalk-go/rpc&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// State contains a map of usernames to callers,
</span></span></span><span class="line"><span class="cl"><span class="c1">// which are used as a callback client to that user
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">State</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">users</span> <span class="nx">sync</span><span class="p">.</span><span class="nx">Map</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Users gets a list of usernames from the keys of the sync.Map
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">s</span> <span class="o">*</span><span class="nx">State</span><span class="p">)</span> <span class="nf">Users</span><span class="p">()</span> <span class="p">(</span><span class="nx">users</span> <span class="p">[]</span><span class="kt">string</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">s</span><span class="p">.</span><span class="nx">users</span><span class="p">.</span><span class="nf">Range</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">k</span><span class="p">,</span> <span class="nx">v</span> <span class="kd">interface</span><span class="p">{})</span> <span class="kt">bool</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">users</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">users</span><span class="p">,</span> <span class="nx">k</span><span class="p">.(</span><span class="kt">string</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">	<span class="p">})</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Join adds a username and caller using the injected rpc.Call
</span></span></span><span class="line"><span class="cl"><span class="c1">// value, then broadcasts the change
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">s</span> <span class="o">*</span><span class="nx">State</span><span class="p">)</span> <span class="nf">Join</span><span class="p">(</span><span class="nx">username</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">c</span> <span class="o">*</span><span class="nx">rpc</span><span class="p">.</span><span class="nx">Call</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">s</span><span class="p">.</span><span class="nx">users</span><span class="p">.</span><span class="nf">Store</span><span class="p">(</span><span class="nx">username</span><span class="p">,</span> <span class="nx">c</span><span class="p">.</span><span class="nx">Caller</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">s</span><span class="p">.</span><span class="nf">broadcast</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Leave removes the user from the sync.Map and broadcasts
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">s</span> <span class="o">*</span><span class="nx">State</span><span class="p">)</span> <span class="nf">Leave</span><span class="p">(</span><span class="nx">username</span> <span class="kt">string</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">s</span><span class="p">.</span><span class="nx">users</span><span class="p">.</span><span class="nf">Delete</span><span class="p">(</span><span class="nx">username</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">s</span><span class="p">.</span><span class="nf">broadcast</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// broadcast uses the rpc.Caller values to perform a callback
</span></span></span><span class="line"><span class="cl"><span class="c1">// with the &#34;state&#34; selector, passing the current list of
</span></span></span><span class="line"><span class="cl"><span class="c1">// usernames. any callers that return an error are added to
</span></span></span><span class="line"><span class="cl"><span class="c1">// gone and then removed with Leave
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">s</span> <span class="o">*</span><span class="nx">State</span><span class="p">)</span> <span class="nf">broadcast</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">users</span> <span class="o">:=</span> <span class="nx">s</span><span class="p">.</span><span class="nf">Users</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">gone</span> <span class="p">[]</span><span class="kt">string</span>
</span></span><span class="line"><span class="cl">	<span class="nx">s</span><span class="p">.</span><span class="nx">users</span><span class="p">.</span><span class="nf">Range</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">k</span><span class="p">,</span> <span class="nx">v</span> <span class="kd">interface</span><span class="p">{})</span> <span class="kt">bool</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">_</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">v</span><span class="p">.(</span><span class="nx">rpc</span><span class="p">.</span><span class="nx">Caller</span><span class="p">).</span><span class="nf">Call</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span> <span class="s">&#34;state&#34;</span><span class="p">,</span> <span class="nx">users</span><span class="p">,</span> <span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">k</span><span class="p">.(</span><span class="kt">string</span><span class="p">),</span> <span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">			<span class="nx">gone</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">gone</span><span class="p">,</span> <span class="nx">k</span><span class="p">.(</span><span class="kt">string</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">})</span>
</span></span><span class="line"><span class="cl">	<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">u</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">gone</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">s</span><span class="p">.</span><span class="nf">Leave</span><span class="p">(</span><span class="nx">u</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="c1">// create a tcp listener
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">l</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">net</span><span class="p">.</span><span class="nf">Listen</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span> <span class="s">&#34;localhost:9999&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// setup a server using fn.HandlerFrom to
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// handle methods from the state value
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">srv</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="nx">rpc</span><span class="p">.</span><span class="nx">Server</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Codec</span><span class="p">:</span>   <span class="nx">codec</span><span class="p">.</span><span class="nx">JSONCodec</span><span class="p">{},</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Handler</span><span class="p">:</span> <span class="nx">fn</span><span class="p">.</span><span class="nf">HandlerFrom</span><span class="p">(</span><span class="nb">new</span><span class="p">(</span><span class="nx">State</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// serve until the listener closes
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">srv</span><span class="p">.</span><span class="nf">Serve</span><span class="p">(</span><span class="nx">l</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span></code></pre><p>The Call pointer that handlers can receive has a reference to a Caller, which is a client to make calls back to the caller, allowing callbacks.</p>
<p>Our client is straightforward. After setting up a connection and a handler to receive and display an updated username listing, we call Join with a username, wait for SIGINT, and call Leave before exiting.</p>
<pre tabindex="0" class="chroma"><code class="language-go"><span class="line"><span class="cl"><span class="c1">// client.go
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;context&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;flag&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;fmt&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;log&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;os&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;os/signal&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/progrium/qtalk-go/codec&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/progrium/qtalk-go/fn&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/progrium/qtalk-go/rpc&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/progrium/qtalk-go/talk&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">fatal</span><span class="p">(</span><span class="nx">err</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">flag</span><span class="p">.</span><span class="nf">Parse</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// establish connection to server
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">client</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">talk</span><span class="p">.</span><span class="nf">Dial</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span> <span class="s">&#34;localhost:9999&#34;</span><span class="p">,</span> <span class="nx">codec</span><span class="p">.</span><span class="nx">JSONCodec</span><span class="p">{})</span>
</span></span><span class="line"><span class="cl">	<span class="nf">fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// state callback handler that redraws the user list
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">client</span><span class="p">.</span><span class="nf">Handle</span><span class="p">(</span><span class="s">&#34;state&#34;</span><span class="p">,</span> <span class="nx">rpc</span><span class="p">.</span><span class="nf">HandlerFunc</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">r</span> <span class="nx">rpc</span><span class="p">.</span><span class="nx">Responder</span><span class="p">,</span> <span class="nx">c</span> <span class="o">*</span><span class="nx">rpc</span><span class="p">.</span><span class="nx">Call</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="kd">var</span> <span class="nx">users</span> <span class="kd">interface</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">c</span><span class="p">.</span><span class="nf">Receive</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">users</span><span class="p">);</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// the nonsense are terminal escape codes
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="c1">// to return to the last line and clear it
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;\u001B[1A\u001B[K&#34;</span><span class="p">,</span> <span class="nx">users</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// respond to incoming calls
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="k">go</span> <span class="nx">client</span><span class="p">.</span><span class="nf">Respond</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// call Join passing a username from arguments
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">_</span><span class="p">,</span> <span class="nx">err</span> <span class="p">=</span> <span class="nx">client</span><span class="p">.</span><span class="nf">Call</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span> <span class="s">&#34;Join&#34;</span><span class="p">,</span> <span class="nx">fn</span><span class="p">.</span><span class="nx">Args</span><span class="p">{</span><span class="nx">flag</span><span class="p">.</span><span class="nf">Arg</span><span class="p">(</span><span class="mi">0</span><span class="p">)},</span> <span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nf">fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// wait until we get SIGINT
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">ch</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="nx">os</span><span class="p">.</span><span class="nx">Signal</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">signal</span><span class="p">.</span><span class="nf">Notify</span><span class="p">(</span><span class="nx">ch</span><span class="p">,</span> <span class="nx">os</span><span class="p">.</span><span class="nx">Interrupt</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="o">&lt;-</span><span class="nx">ch</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// call Leave before finishing
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="nx">_</span><span class="p">,</span> <span class="nx">err</span> <span class="p">=</span> <span class="nx">client</span><span class="p">.</span><span class="nf">Call</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span> <span class="s">&#34;Leave&#34;</span><span class="p">,</span> <span class="nx">fn</span><span class="p">.</span><span class="nx">Args</span><span class="p">{</span><span class="nx">flag</span><span class="p">.</span><span class="nf">Arg</span><span class="p">(</span><span class="mi">0</span><span class="p">)},</span> <span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nf">fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span></code></pre><p>See the <a href="https://github.com/progrium/qtalk-go/wiki/Examples">Examples wiki page</a> for more code examples, including tunnels and proxies, selector routing, and streaming responses.</p>
<h2>Roadmap</h2>
<p>I'm trying to get to a 1.0 for <a href="https://github.com/progrium/qtalk-go">qtalk-go</a>, so I'd like more people to use and review its code. I also haven't actually gotten around to <a href="https://github.com/progrium/qtalk-go/issues/2">putting in QUIC</a> as a usable base layer, which I think should be in a 1.0 release. It's in the name, qtalk was started with QUIC in mind. Not only will QUIC improve performance, resolve head of line blocking, and eventually be native to browsers, but being UDP-based means that hole punching can be used to establish peer-to-peer qtalk connections. I'd like to one day be able to use qtalk directly between machines behind NAT.</p>
<p>Meanwhile, I'm wrapping up a JavaScript implementation (in TypeScript) to officially release soon. I have the start of a Python implementation I could use help with, and I'd love to have a C# implementation.</p>
<p>That's it for now. A big thanks to my <a href="https://github.com/sponsors/progrium">sponsors</a> for making this happen and thanks to you for reading!</p>
<hr>
<p><em>For more great posts like this sent directly to your inbox and to find out what all I'm up to, get on the list at <a href="http://progrium.com">progrium.com</a> ✌️</em></p>
]]></content:encoded></item><item><title>Towards the Personal Potential of Software</title><link>https://progrium.xyz/articles/2021/towards-the-personal-potential-of-software</link><guid isPermaLink="true">https://progrium.xyz/articles/2021/towards-the-personal-potential-of-software</guid><pubDate>Mon, 05 Jul 2021 00:00:00 GMT</pubDate><description>A midyear review and preview of what's coming.</description><content:encoded><![CDATA[<p>Since starting work under <a href="http://progrium.com">Progrium Technology Company</a>, I've had a single objective: to build a system called Tractor. It's what I've been slowly approaching in my dense 22 year career as an independent programmer. The Tractor System is still hard for me to describe at this stage, but its goal is to make building production-grade personal software systems like building with Legos: fast and fun.</p>
<p>After a difficult year heads-down in 2020, I started posting here in January with the intention of sharing and officially releasing components I've been working on that are building up to Tractor. Besides warming up for when I'd eventually talk about Tractor, I wanted people to see how I make independently useful building blocks towards an ideal. That's a core part of the Tractor philosophy.</p>
<p>Let's review what I've shared so far, and then set up what's coming in the next 6 months.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k38vrxqkzvmr9ek72l3i.gif" alt="MacDriver"></p>
<p>The first big release of the year was the <a href="https://github.com/progrium/macdriver">macdriver</a> project, which got a significant response on Hacker News. It gives us Objective-C and Apple framework bindings for Go, letting you build simple Apple apps entirely from Go. That wasn't possible before, so it was a little exciting, but it was early. The native Go APIs included for commonly used Foundation and Cocoa classes were far from complete. They could still be used if you knew what you were doing, but with an ideal of total coverage of Apple frameworks, that wouldn't be enough. There were also unresolved issues just deciding how to best manage memory and pointers, which I knew would fall on me to figure out and take some time.</p>
<p>Luckily I've since gotten a colleague involved in the company, and he's been helping push <a href="https://github.com/progrium/macdriver">macdriver</a> towards a real beta. Part of this was made possible by a project I spun out of macdriver and <a href="https://dev.to/progrium/apple-api-schemas-for-code-generation-and-more-1phj">posted about</a>, though again in a very early state. That project was <a href="https://github.com/progrium/macschema">macschema</a>.</p>
<p>With the <a href="https://github.com/progrium/macschema">macschema</a> toolchain, you can generate API schemas about any Apple framework, class, function, etc based on their documentation and header declarations. This is useful for us in generating framework bindings in macdriver, but it would be useful for any project doing similar work, such as bindings for other languages. This sort of approach will play a big part in how Tractor will integrate with &quot;whatever we want&quot; down the line.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/plpudhz4v7dponsucy91.gif" alt="topframe"></p>
<p>Along the way, I released a few demos for macdriver to make clear the possibilities and provide reference examples. One of them I spun off into a standalone project called <a href="https://github.com/progrium/topframe">topframe</a>, which is also serving as a test bed for how we are doing cross-platform support. More on that in a moment.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c2xo2fuvpkt30kfsrprr.jpeg" alt="Multiplexing"></p>
<p>Unrelated to macdriver, I released a protocol called <a href="https://github.com/progrium/qmux">qmux</a> with <a href="https://dev.to/progrium/the-history-and-future-of-socket-level-multiplexing-1d5n">a post</a> explaining why this (but really any) muxing protocol, including and especially QUIC, is such a great primitive for network programming. The project came with two implementations, Go and TypeScript, with more on the way. The post about it was the first explainer article I'd done in a while and people liked it. This was important because the idea behind it was really more valuable than the protocol itself, but regardless we still need implementations of it to exist. To show the idea in action, I built a <a href="https://github.com/progrium/qmux/tree/main/demos/groktunnel">130 line version of Ngrok</a>, which turned into <a href="https://dev.to/progrium/building-your-own-ngrok-in-130-lines-2lif">a great post</a> walking through how it works.</p>
<p>That brings us to today, half way through 2021. Let's talk about what's coming.</p>
<p>More exciting than qmux is what we built on top of it, which is called <em>qtalk</em>. This is my meticulously designed, re-written-several-times network/IPC programming stack. While not <em>that</em> different from just JSON-RPC with pluggable codecs, its two unique features are callbacks and streams. Callbacks means its bi-directional, either side can expose or call methods. Just that alone is something rare in existing RPC stacks, but necessary for callbacks. And while streaming RPC is not new, our streams are full virtual connections, so you can stream more RPC results, arbitrary byte streams, or tunnel something else like a database connection. I'll share more about the possibilities when it's released.</p>
<p>So in the second half of 2021, we'll see qtalk, we'll see a major update to macdriver, and then with those two parts we can release <em>shelldriver</em>. This is a cross platform API to platform specific resources like windows, dialogs, menus, etc. It's about native GUI shell integration, it's not about having every native UI component. In fact, for app UI it really focuses on windows and webviews, a la Electron. Unlike Electron, it can be used from any language that has a qmux and qtalk implementation. The result should be a simple &quot;Electron but as a library&quot; that you can use from Go and other languages.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y7qq2kea7hvdk83ughuk.png" alt="shelldriver"></p>
<p>Once shelldriver is out I will be able to finally start talking about the first <em>real</em> piece of the Tractor system: Tractor Toolkit. I hope to have at least a public demo by the end of the year. The Toolkit is already being shown to friends and sponsors. The work I've shared so far is just the tip of the iceberg.</p>
<p>Which reminds me ...</p>
<p>I really, really have to thank my <a href="https://github.com/sponsors/progrium">sponsors</a>. As things are ramping up, I'm starting to work with more people and this is all self funded. Not only does sponsorship support all this open source work, but sponsors get early access to Tractor Toolkit <em>and</em> see more of what I'm up to.</p>
<p>Also to you, thanks for reading and following along. I'll be back soon with more releases.</p>
<p><em>For more posts like this sent directly to your inbox and to stay on top of what I'm up to, get on the list at <a href="http://progrium.com">progrium.com</a> ✌️</em></p>
]]></content:encoded></item><item><title>Building your own Ngrok in 130 lines</title><link>https://progrium.xyz/articles/2021/building-your-own-ngrok-in-130-lines</link><guid isPermaLink="true">https://progrium.xyz/articles/2021/building-your-own-ngrok-in-130-lines</guid><pubDate>Thu, 27 May 2021 00:00:00 GMT</pubDate><description>Remote port forwarding as a service</description><content:encoded><![CDATA[<p>Running a local development server for your app is pretty common, but what if you wanted somebody else to access it? Maybe for a demo, or maybe to debug webhook integrations. If you've ever used <a href="https://ngrok.com/">Ngrok</a> (or perhaps the original <a href="https://github.com/progrium/localtunnel">localtunnel</a>), you know what I'm talking about.</p>
<p>Today we're going to make a system that emulates the core functionality of Ngrok, exposing a localhost HTTP server via a public URL.</p>
<p>Just to make sure we're clear on how this all works, let me explain the problem. Normally when you run a server on your computer it can only be accessed by other computers on the same local network. However, many development servers will bind to <code>127.0.0.1</code>, or <code>localhost</code>, which means only a client or browser on your computer can access it. Either way, there are times when you might want another party to access it temporarily, but it's simply not addressable from the public Internet.</p>
<p>In the olden days, this could only be achieved if you had access to a server running SSH. You could configure SSH to have publicly addressable ports on that server be &quot;forwarded&quot; to a local port on your computer over an SSH connection. This is called <a href="https://help.ubuntu.com/community/SSH/OpenSSH/PortForwarding">remote port forwarding</a> or sometimes SSH tunneling. Assuming SSH is configured properly, the command to set this up looks like this:</p>
<pre><code>$ ssh -R 8080:localhost:3000 your-ssh-server.com
</code></pre>
<p>This would allow somebody to point their browser to <code>your-ssh-server.com:8080</code> and get your localhost server running on port <code>3000</code> as long as this command was running. Around 2009, I noticed how useful this is (especially in the context of webhooks), but was annoyed you had to have an SSH server properly configured for it before you could even do it. Even then, I always found the SSH command difficult to remember how to use for forwarding. I envisioned a command as simple as:</p>
<pre><code>$ localtunnel 3000
</code></pre>
<p>So I set about making this happen. My <a href="https://github.com/progrium/localtunnel/tree/prototype">first attempt</a> at building a system with Twisted Python failed because I didn't quite grok <a href="https://dev.to/progrium/the-history-and-future-of-socket-level-multiplexing-1d5n">multiplexing</a>. So I ended up wrapping SSH. I wrote a <code>localtunnel</code> client in Ruby that behind the scenes would just run the above SSH command using a server I had set up. The only difference being that I didn't give you a port like <code>8080</code> to connect to, I wanted you to get a generated subdomain. Instead of <code>localtunnel.com:8080</code>, you'd get <code>wxyz.localtunnel.com</code>, which was unique to your tunnel as it was intended as a public service.</p>
<p>To achieve that, I just ran a Twisted web server with the SSH server that would figure out which port to use based on the subdomain and proxy to it. This would only work with HTTP because the idea of virtual hosts was actually invented for HTTP. Normally when you get a TCP connection you have no idea what hostname is used.</p>
<p>So I had something like this:
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wwljug8u9scfmlk1qutg.png" alt="Localtunnel Topology"></p>
<p>And it worked! It became rather popular. However, due to the slight complexity (Ruby+Python+OpenSSH) and the fact I was too busy to maintain the service, people started cloning it. One jackass even called <a href="https://theboroer.github.io/localtunnel-www/">his clone</a> &quot;localtunnel&quot;.</p>
<p>Another jackass, who was less of a jackass and more my friend and coworker, decided to clone it as a way to learn Go. He called it Ngrok and decided to commercialize it. This bothered me for a number of reasons I won't get into, but eventually decided it was fine since he really improved on the idea and would at least listen to me if I had anything to say about it.</p>
<p><em>Anyway</em>, today we're exploring a toy recreation of this system that we'll call groktunnel. It removes SSH from the setup and using two libraries lets us build the whole thing in about 130 lines of Go. You'd still need a server to run this on, so it's not as convenient as a hosted service, but this is more for educational purposes. After all, groktunnel is actually a demo for qmux, one of the libraries I mentioned. Let me explain these libraries.</p>
<p>What <a href="https://github.com/progrium/qmux">qmux</a> does is give us a subset of the SSH protocol to multiplex many connections over a single connection. It was the missing piece of my original Twisted prototype. You can read more about how there aren't a lot of these protocols (but perhaps with QUIC are also the future of the Internet) in my <a href="https://dev.to/progrium/the-history-and-future-of-socket-level-multiplexing-1d5n">previous post</a>.</p>
<p>The other library is actually by the author of Ngrok. Called <a href="https://github.com/inconshreveable/go-vhost">go-vhost</a>, it helps with the problem of virtual hosts. Ideally when a connection comes in to be forwarded down the tunnel we can hand it off wholesale. This way the other end can just start reading off the connection as if received directly. But in order to figure out the hostname used, we have to start reading off the connection up to the Host header. Then we have to hand it off prepended with what was read. What Alan wrote is an abstraction that gives you a virtual listener that lets you accept new connections for a particular virtual host and get the connection as if it hadn't been read yet. A lot of this is due to the interface based approach of the Go standard library.</p>
<p>This brings us to the main utility function we'll be writing for this system. A TCP connection established between two peers cannot (easily) be handed off between programs, let alone hosts. We don't literally &quot;forward&quot; the connection, we sit in the middle and relay the bytes. This is how every proxy, load balancer, gateway, or tunnel works. It sort of welds two sockets together. I call this socket joining. And if you <em>can</em> avoid doing any protocol level stuff, building a proxy just becomes opening a connection to the backend when a connection comes in and then joining them.</p>
<p>Since Go models connections using the interfaces of the <code>io</code> package, we can use <code>io.Copy</code> to simplify reading off bytes from one connection into another until an <code>EOF</code>. A two-way copy like this that closes when finished looks like this:</p>
<pre tabindex="0" class="chroma"><code class="language-go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">join</span><span class="p">(</span><span class="nx">a</span> <span class="nx">io</span><span class="p">.</span><span class="nx">ReadWriteCloser</span><span class="p">,</span> <span class="nx">b</span> <span class="nx">io</span><span class="p">.</span><span class="nx">ReadWriteCloser</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">go</span> <span class="nx">io</span><span class="p">.</span><span class="nf">Copy</span><span class="p">(</span><span class="nx">b</span><span class="p">,</span> <span class="nx">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">io</span><span class="p">.</span><span class="nf">Copy</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">a</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">	<span class="nx">b</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>This minimal approach benefits from setting a deadline on the connections in case one end doesn't hang up. You can see we copy bytes from <code>a</code> to <code>b</code> in the background, then wait for copying bytes from <code>b</code> to <code>a</code> before making sure both sides are closed once <code>b</code> gets an <code>EOF</code>.</p>
<p>We'll also define a helper to generate random subdomains. In a production service this is a big deal. You'd want an incredibly large space of possible subdomains, but here we generate 10 random alphanumeric characters using <code>crypto/rand</code>:</p>
<pre tabindex="0" class="chroma"><code class="language-go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">newSubdomain</span><span class="p">()</span> <span class="kt">string</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">b</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">byte</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">rand</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="nx">b</span><span class="p">);</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="nx">letters</span> <span class="o">:=</span> <span class="p">[]</span><span class="nb">rune</span><span class="p">(</span><span class="s">&#34;abcdefghijklmnopqrstuvwxyz1234567890&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">r</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">rune</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">r</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">r</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="p">=</span> <span class="nx">letters</span><span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="nx">b</span><span class="p">[</span><span class="nx">i</span><span class="p">])</span><span class="o">*</span><span class="nb">len</span><span class="p">(</span><span class="nx">letters</span><span class="p">)</span><span class="o">/</span><span class="mi">256</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nb">string</span><span class="p">(</span><span class="nx">r</span><span class="p">)</span> <span class="o">+</span> <span class="s">&#34;.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>Another quick utility to define that's common in simple Go programs is a fatal function. This helps simplify all the error checking.</p>
<pre tabindex="0" class="chroma"><code class="language-go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">fatal</span><span class="p">(</span><span class="nx">err</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>Now we can get started in our <code>main()</code> which will contain both client and server modes. However, first we'll define some flags useful to both:</p>
<pre tabindex="0" class="chroma"><code class="language-go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">port</span> <span class="p">=</span> <span class="nx">flag</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="s">&#34;p&#34;</span><span class="p">,</span> <span class="s">&#34;9999&#34;</span><span class="p">,</span> <span class="s">&#34;server port to use&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">host</span> <span class="p">=</span> <span class="nx">flag</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="s">&#34;h&#34;</span><span class="p">,</span> <span class="s">&#34;vcap.me&#34;</span><span class="p">,</span> <span class="s">&#34;server hostname to use&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">addr</span> <span class="p">=</span> <span class="nx">flag</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="s">&#34;b&#34;</span><span class="p">,</span> <span class="s">&#34;127.0.0.1&#34;</span><span class="p">,</span> <span class="s">&#34;ip to bind [server only]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">flag</span><span class="p">.</span><span class="nf">Parse</span><span class="p">()</span>
</span></span></code></pre><p>The default hostname of <code>vcap.me</code> is a way to make the server easy to run locally. Since <code>localhost</code> doesn't allow subdomains, but any domain can resolve to <code>127.0.0.1</code>, there are a few public domains that have wildcard subdomains all resolving to <code>127.0.0.1</code>. If we ran this on a real server, we'd need a domain with wildcard subdomains resolving to the server IP, and we'd probably use port <code>80</code> instead of <code>9999</code>.</p>
<p>Now we'll start with the client, which is quite simple. It's activated when there is an argument specifying the local port to expose publicly.</p>
<pre tabindex="0" class="chroma"><code class="language-go"><span class="line"><span class="cl"><span class="c1">// client usage: groktunnel [-h=&lt;server hostname&gt;] &lt;local port&gt;
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="nx">flag</span><span class="p">.</span><span class="nf">Arg</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">!=</span> <span class="s">&#34;&#34;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">conn</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">net</span><span class="p">.</span><span class="nf">Dial</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span> <span class="nx">net</span><span class="p">.</span><span class="nf">JoinHostPort</span><span class="p">(</span><span class="o">*</span><span class="nx">host</span><span class="p">,</span> <span class="o">*</span><span class="nx">port</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">	<span class="nf">fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">client</span> <span class="o">:=</span> <span class="nx">httputil</span><span class="p">.</span><span class="nf">NewClientConn</span><span class="p">(</span><span class="nx">conn</span><span class="p">,</span> <span class="nx">bufio</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">(</span><span class="nx">conn</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">	<span class="nx">req</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">http</span><span class="p">.</span><span class="nf">NewRequest</span><span class="p">(</span><span class="s">&#34;GET&#34;</span><span class="p">,</span> <span class="s">&#34;/&#34;</span><span class="p">,</span> <span class="kc">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">req</span><span class="p">.</span><span class="nx">Host</span> <span class="p">=</span> <span class="nx">net</span><span class="p">.</span><span class="nf">JoinHostPort</span><span class="p">(</span><span class="o">*</span><span class="nx">host</span><span class="p">,</span> <span class="o">*</span><span class="nx">port</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nf">fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">client</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="nx">req</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">resp</span><span class="p">,</span> <span class="nx">_</span> <span class="o">:=</span> <span class="nx">client</span><span class="p">.</span><span class="nf">Read</span><span class="p">(</span><span class="nx">req</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;port %s http available at:\n&#34;</span><span class="p">,</span> <span class="nx">flag</span><span class="p">.</span><span class="nf">Arg</span><span class="p">(</span><span class="mi">0</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">	<span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;http://%s\n&#34;</span><span class="p">,</span> <span class="nx">resp</span><span class="p">.</span><span class="nx">Header</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="s">&#34;X-Public-Host&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">	<span class="nx">c</span><span class="p">,</span> <span class="nx">_</span> <span class="o">:=</span> <span class="nx">client</span><span class="p">.</span><span class="nf">Hijack</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">	<span class="nx">sess</span> <span class="o">:=</span> <span class="nx">session</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="nx">c</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">defer</span> <span class="nx">sess</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">	<span class="k">for</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">ch</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">sess</span><span class="p">.</span><span class="nf">Accept</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">		<span class="nf">fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nx">conn</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">net</span><span class="p">.</span><span class="nf">Dial</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span> <span class="s">&#34;127.0.0.1:&#34;</span><span class="o">+</span><span class="nx">flag</span><span class="p">.</span><span class="nf">Arg</span><span class="p">(</span><span class="mi">0</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">		<span class="nf">fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="k">go</span> <span class="nf">join</span><span class="p">(</span><span class="nx">conn</span><span class="p">,</span> <span class="nx">ch</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>Here we set up the tunnel over HTTP since we'll be serving HTTP anyway, and we need a way to tell the client what the generated public host is to display a URL to the user. You can see we get the hostname from a response header.</p>
<p>We're manually creating an HTTP request with <code>httputil.NewClientConn</code> because it lets us hijack the connection. This simply means it stops processing as HTTP and treats it like a raw TCP connection. Alternatively we could upgrade to WebSocket for this, but this simply works just as well with fewer dependencies.</p>
<p>After hijacking we create a qmux session to start multiplexing virtual connections, or channels, over this connection. This is the tunnel. We don't open channels, we just sit in a loop waiting for incoming channels and assume they want to be joined with a connection to our localhost server. That's it.</p>
<p>Now the server. First we set everything up using our flags, including a TCP listener, which we wrap in a virtual host muxer. The meat of our server is in <code>serve()</code> but that runs asynchronously as we listen for vhost muxing errors until shutdown.</p>
<pre tabindex="0" class="chroma"><code class="language-go"><span class="line"><span class="cl"><span class="c1">// server usage: groktunnel [-h=&lt;hostname&gt;] [-b=&lt;bind ip&gt;]
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">l</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">net</span><span class="p">.</span><span class="nf">Listen</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span> <span class="nx">net</span><span class="p">.</span><span class="nf">JoinHostPort</span><span class="p">(</span><span class="o">*</span><span class="nx">addr</span><span class="p">,</span> <span class="o">*</span><span class="nx">port</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="nf">fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">defer</span> <span class="nx">l</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nx">vmux</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">vhost</span><span class="p">.</span><span class="nf">NewHTTPMuxer</span><span class="p">(</span><span class="nx">l</span><span class="p">,</span> <span class="mi">1</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">go</span> <span class="nf">serve</span><span class="p">(</span><span class="nx">vmux</span><span class="p">,</span> <span class="o">*</span><span class="nx">host</span><span class="p">,</span> <span class="o">*</span><span class="nx">port</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">log</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;groktunnel server [%s] ready!\n&#34;</span><span class="p">,</span> <span class="o">*</span><span class="nx">host</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">conn</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">vmux</span><span class="p">.</span><span class="nf">NextError</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">	<span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">conn</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">conn</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>Now the heart of this system in <code>serve()</code>:</p>
<pre tabindex="0" class="chroma"><code class="language-go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">serve</span><span class="p">(</span><span class="nx">vmux</span> <span class="o">*</span><span class="nx">vhost</span><span class="p">.</span><span class="nx">HTTPMuxer</span><span class="p">,</span> <span class="nx">host</span><span class="p">,</span> <span class="nx">port</span> <span class="kt">string</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">ml</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">vmux</span><span class="p">.</span><span class="nf">Listen</span><span class="p">(</span><span class="nx">net</span><span class="p">.</span><span class="nf">JoinHostPort</span><span class="p">(</span><span class="nx">host</span><span class="p">,</span> <span class="nx">port</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">	<span class="nf">fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">srv</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="nx">http</span><span class="p">.</span><span class="nx">Server</span><span class="p">{</span><span class="nx">Handler</span><span class="p">:</span> <span class="nx">http</span><span class="p">.</span><span class="nf">HandlerFunc</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span> <span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span> <span class="nx">r</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">publicHost</span> <span class="o">:=</span> <span class="nx">strings</span><span class="p">.</span><span class="nf">TrimSuffix</span><span class="p">(</span><span class="nx">net</span><span class="p">.</span><span class="nf">JoinHostPort</span><span class="p">(</span><span class="nf">newSubdomain</span><span class="p">()</span><span class="o">+</span><span class="nx">host</span><span class="p">,</span> <span class="nx">port</span><span class="p">),</span> <span class="s">&#34;:80&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nx">pl</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">vmux</span><span class="p">.</span><span class="nf">Listen</span><span class="p">(</span><span class="nx">publicHost</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nf">fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nx">w</span><span class="p">.</span><span class="nf">Header</span><span class="p">().</span><span class="nf">Add</span><span class="p">(</span><span class="s">&#34;X-Public-Host&#34;</span><span class="p">,</span> <span class="nx">publicHost</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nx">w</span><span class="p">.</span><span class="nf">Header</span><span class="p">().</span><span class="nf">Add</span><span class="p">(</span><span class="s">&#34;Connection&#34;</span><span class="p">,</span> <span class="s">&#34;close&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nx">conn</span><span class="p">,</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">_</span> <span class="o">:=</span> <span class="nx">w</span><span class="p">.(</span><span class="nx">http</span><span class="p">.</span><span class="nx">Hijacker</span><span class="p">).</span><span class="nf">Hijack</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">		<span class="nx">sess</span> <span class="o">:=</span> <span class="nx">session</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="nx">conn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="k">defer</span> <span class="nx">sess</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">		<span class="nx">log</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;%s: start session&#34;</span><span class="p">,</span> <span class="nx">publicHost</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="k">go</span> <span class="kd">func</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">for</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="nx">conn</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">pl</span><span class="p">.</span><span class="nf">Accept</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">				<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">					<span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">					<span class="k">return</span>
</span></span><span class="line"><span class="cl">				<span class="p">}</span>
</span></span><span class="line"><span class="cl">				<span class="nx">ch</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">sess</span><span class="p">.</span><span class="nf">Open</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">				<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">					<span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">					<span class="k">return</span>
</span></span><span class="line"><span class="cl">				<span class="p">}</span>
</span></span><span class="line"><span class="cl">				<span class="k">go</span> <span class="nf">join</span><span class="p">(</span><span class="nx">ch</span><span class="p">,</span> <span class="nx">conn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="p">}()</span>
</span></span><span class="line"><span class="cl">		<span class="nx">sess</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">		<span class="nx">log</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;%s: end session&#34;</span><span class="p">,</span> <span class="nx">publicHost</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">})}</span>
</span></span><span class="line"><span class="cl">	<span class="nx">srv</span><span class="p">.</span><span class="nf">Serve</span><span class="p">(</span><span class="nx">ml</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>Here we create a new listener using the vhost muxer passed in to listen for connections on the naked domain (<code>vcap.me</code>). Then we make an HTTP handler that sets up a new tunnel. First it generates a new hostname and tells our vhost muxer to start listening on it. We'll hold on to that listener.</p>
<p>Then we start writing headers. Remember this is how we tell the client what our generated hostname is. We also use <code>Connection: close</code> to tell the other end we're going to be done and normally hang up, but after we flush the headers with a <code>200 OK</code> status, we hijack the connection like we know is going to happen on the other end. We make a new qmux session and start a new goroutine.</p>
<p>This goroutine handles connections coming in from our new vhost listener. All this does is sit in a loop accepting new connections, then using our qmux session opens a channel, then joins the new connection with that channel.</p>
<p>Back in our HTTP handler, we call <code>Wait()</code> on our session, which will just block until the connection breaks. This handler has been put into an HTTP server instance set to serve on our original vhost listener for the naked <code>vcap.me</code> domain.</p>
<p>Now we can try it out. You can follow the instructions in the <a href="https://github.com/progrium/qmux/tree/main/demos/groktunnel#readme">groktunnel README</a> to see it in action.</p>
<p>This little program gives us a client and server that recreates the original localtunnel system. It showcases the use case of tunneling for connection multiplexing, but also how easily you can wire things up in Go using mostly the standard library.</p>
<p>If you wanted to tunnel normal TCP connections, not HTTP connections, this program gets even simpler. However, that's an exercise for the reader. You can see the <a href="https://github.com/progrium/qmux/tree/main/demos/groktunnel">full groktunnel source</a> in the demos directory of the <a href="https://github.com/progrium/qmux">qmux</a> project. Hope you were inspired to build something cool!</p>
<p><em>For more posts like this sent directly to your inbox and to find out what all I'm up to, get on the list at <a href="http://progrium.com">progrium.com</a> ✌️</em></p>
]]></content:encoded></item><item><title>The History and Future of Socket-level Multiplexing</title><link>https://progrium.xyz/articles/2021/the-history-and-future-of-socket-level-multiplexing</link><guid isPermaLink="true">https://progrium.xyz/articles/2021/the-history-and-future-of-socket-level-multiplexing</guid><pubDate>Thu, 06 May 2021 00:00:00 GMT</pubDate><description>Towards a socket API with builtin stream muxing</description><content:encoded><![CDATA[<p>Multiplexing (sometimes called muxing) has a long history of innovation in telecommunications. In 1875, Edison figured out how to transmit four separate telegraph lines over a single wire. In 1910, multiplexing technology was brought to telephony. Within 30 years the Bell System could multiplex 2,400 channels over a single cable. Then in the 1960s we got packet switching, a kind of unlimited multiplexing, which led to computer networking and Internet protocols.</p>
<p>I think of layered protocols and packet switching as &quot;virtualized&quot; multiplexing. Every layer provides a new medium or virtual transport for more protocols to operate, all over the same physical medium. Ethernet allows IPX, IP, and other packets. IP then allows UDP, TCP, and other packets. TCP allows HTTP, SSH, and so on.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qqgzngwcbyt81uav77w.png" alt="Screen Shot 2021-05-06 at 10.56.29 AM"></p>
<p>The layers nearly end at TCP and UDP because these two protocols provide most of what any programmer would need to make two programs talk. This was more or less set in stone in the 1980s when Unix introduced the <a href="https://en.wikipedia.org/wiki/Berkeley_sockets">socket API</a>, which has been the de facto standard interface for network programming ever since.</p>
<p>This API layer is where you get &quot;application protocols&quot; like FTP, Telnet, DNS, and so many others. TCP, UDP, and the layers below were typically concerned with just getting bytes from one point to another. Application protocols introduced more specific interactions like request-reply, such as with HTTP. Some, like SSH and DNS, became rather multifaceted protocols to serve their domain with many different flows and message types. Rarely would you find an application protocol multiplexed to provide a generic transport for another.</p>
<p>An early exception was SSH. One of the reasons SSH fascinates me is that it was designed for many uses and breaks down into its own set of layers and internal protocols, almost mirroring TLS, TCP, and UDP.</p>
<p>First, SSH establishes an authenticated and encrypted line, similar to TLS. Then you have two very generic ways to communicate over this line. You can send &quot;request&quot; messages that may or may not get a response, similar to use cases of UDP. Or you can establish &quot;channels&quot; that were bidirectional streams just like TCP.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tigca329wjs28z6mowf6.png" alt="Screen Shot 2021-05-06 at 11.01.03 AM"></p>
<p>From there, SSH defines how to use these requests and channels as primitives to set up remote shells. But besides that, you could also forward TCP connections, which worked by bridging a TCP socket with an SSH channel, &quot;tunneling&quot; the TCP connection through the SSH connection.</p>
<p>If you've ever used <a href="https://ngrok.com/">Ngrok</a> to open a public endpoint to a localhost server, you may not know it was one of several clones of a tool I made in 2010 called <a href="https://github.com/progrium/localtunnel/">localtunnel</a>. The original localtunnel was just a wrapper around SSH, literally using OpenSSH on the server side.</p>
<p>So SSH was one of the earliest application level protocols designed to further multiplex its transport protocol. The next protocol to multiplex at the application/socket level was HTTP/2. It was introduced in 2015, which is still quite recent, but 20 years after SSH.</p>
<p>HTTP has become a new de facto API, like Unix sockets did. What was originally used to just download files into the browser has been co-opted and repurposed. As people started building more and more complex applications in the browser and with the emergence of web APIs, HTTP evolved into a sort of &quot;do everything&quot; protocol.</p>
<p>In 2012, Google started an effort to design a more efficient HTTP with what they called SPDY, which they were able to test end-to-end in Chrome without people even knowing. After proving its improved performance and cost saving benefits, SPDY basically became HTTP/2. They did a number of things to make it more efficient, but the core idea was multiplexing.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gso8h95hlmh3uegd61d3.png" alt="image_1"></p>
<p>HTTP/2 was more or less HTTP, but over newly introduced multiplexed &quot;streams&quot; similar to normal TCP connections. This makes them roughly the equivalent of SSH &quot;channels&quot;. At least at one point, Ngrok used a subset of HTTP/2 as their muxing protocol the same way localtunnel used SSH.</p>
<p>Both HTTP/2 and SSH do more than multiplexing, and usually aren't thought of specifically for muxing. Most library implementations don't expose the lower level multiplexing primitives. SSH at least has clear separation in its specification, where multiplexed channels are a specific and independent layer of the protocol architecture.</p>
<p>After spending so much time with these multiplexing protocols, I noticed something. Most other application protocols also did something similar to multiplexing without realizing.</p>
<p>For example, any request-reply protocol that allows more than one request over a connection is &quot;multiplexing&quot; each request and reply pair. A request and response will often get a shared ID so they can be correlated as other requests and responses can be sent in the meantime. This is the heart of any muxing protocol!</p>
<p>I've found implementing RPC or any request-reply over a muxing API not only simplifies the protocol, but opens up the ability to also stream or tunnel other protocols on the same connection. You could &quot;attach&quot; a tunneled database connection with an RPC response.</p>
<p>Working with muxed streams over a single connection gives me a similar feeling to discovering green threads for the first time. Suddenly, streams between the same two points are so cheap and lightweight you can think about using them in totally different ways.</p>
<p>I now believe a socket API with muxing builtin is a better base layer and API for designing application protocols. I was validated when Google pushed multiplexing into <a href="https://en.wikipedia.org/wiki/QUIC">QUIC</a>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i58vx6qviqe4r6vpzo2g.png" alt="3-Figure3-1"></p>
<p>If Google could redesign HTTP, why not also TCP+TLS? Their experience with SPDY started them down a path to reduce the overhead of TLS and TCP session establishment even more by creating a more efficient kind of encrypted TCP over UDP.  Built into this layer is stream muxing that would not suffer from <a href="https://en.wikipedia.org/wiki/Head-of-line_blocking">head of line blocking</a>.</p>
<p>It's taken nearly 10 years for QUIC to be refined and adopted in the wild and we're basically there. There's even a new browser API in the works called <a href="https://w3c.github.io/webtransport/">WebTransport</a>.</p>
<p>In the meantime, I started designing protocols on the assumption of having a muxing layer directly beneath. Eventually, that would be QUIC, but as a stopgap, what would it be? Remember, there haven't been any standard TCP muxing protocols.</p>
<p>I considered what Ngrok did with a stripped down HTTP/2, but then I realized... SSH has been around a lot longer. SSH is known to work on nearly every platform and is trusted by basically everybody. Best of all, the SSH muxing layer is simpler and more straightforward than HTTP/2.</p>
<p>So I literally took the muxing chapter of the SSH spec, cut out a couple of SSH specific identifiers, stripped it down to just the channels, and I've been using it for the past 5 years. It's called <a href="https://github.com/progrium/qmux">qmux</a>.</p>
<p>The great thing is that even with QUIC available, qmux can be used over any streaming transport and provide the same foundation for higher level protocols built on muxing. Whether the transport is WebSocket or <code>stdio</code>, as long as it can reliably transport ordered byte streams, qmux drops right in. Protocols built on it will translate easily to QUIC.</p>
<p>I'm only releasing this now because it might not have made sense without all this context or more awareness of QUIC. The banality of a brand new subset of a 25 year old protocol does not escape me, but it's the shift in perspective that brings potential. In that way it's similar to webhooks!</p>
<p>If nothing else, I hope people use this story as inspiration to think about using multiplexing to design better, simpler application protocols. Even if for some reason QUIC isn't the future, you now have <a href="https://github.com/progrium/qmux">qmux</a>!</p>
<p><em>For more great posts like this sent directly to your inbox and to find out what all I'm up to, get on the list at <a href="http://progrium.com">progrium.com</a></em> ✌️</p>
]]></content:encoded></item><item><title>Apple API schemas for code generation and more</title><link>https://progrium.xyz/articles/2021/apple-api-schemas-for-code-generation-and-more</link><guid isPermaLink="true">https://progrium.xyz/articles/2021/apple-api-schemas-for-code-generation-and-more</guid><pubDate>Fri, 16 Apr 2021 00:00:00 GMT</pubDate><description>Why I love API schemas and built a tool to generate schemas from Apple documentation.</description><content:encoded><![CDATA[<p>Schemas are pretty dope. My appreciation for API schemas as a toolmaker comes from a desire to build tools that can integrate with many systems, and open new avenues for people to use APIs.</p>
<p>For most, the use case that comes to mind for API schemas is generating documentation. Translating a machine-readable schema into a human friendly representation is a scalable, automated way to maintain solid reference documentation. Although many systems exist to generate documentation directly from code, having an intermediary representation is useful for other use cases.</p>
<p>Code generation is the other big one. It's common for companies like Google and Amazon to generate client libraries across languages for their many web APIs. Even some libraries for GitHub's API are generated because their API has gotten so big it would be untenable otherwise. This is particularly important in the open source community where maintainer time is a scarce resource. Schemas save a lot of time, but it's still pretty uncommon to find a schema for any given web API.</p>
<p>Even less common are schemas for non-web APIs. In 2016, <a href="https://www.electronjs.org/blog/api-docs-json-schema">Electron started releasing a JSON schema</a> of all their APIs that allowed me to build a bridge to use Electron APIs from Go. I had that prototype in mind when I started the <a href="https://github.com/progrium/macdriver">macdriver</a> project that was released a couple months ago. Right now we're manually wrapping Apple framework classes with Go types so you can write native Apple platform applications that look like this:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5qspum8s6zvhbnzvw3le.png" alt="macdriver example"></p>
<p>These kinds of bindings used to be more supported by Apple as evidenced by bridgesupport files, which were XML schemas of their APIs. However, they aren't very well supported any longer. Moreover, bridgesupport files were more like headers and didn't provide much in the form of documentation.</p>
<p>So if we wanted to generate the APIs in macdriver, we could parse Objective-C headers, or maybe try to use what's available in bridgesupport files. But we'd also like to generate human readable descriptions and ideally link to official docs.</p>
<p><strong>If schemas are used to generate documentation, there's no reason you can't generate schemas from documentation.</strong></p>
<p>So I built a tool that could parse Apple documentation into schemas. Since their docs also have the declarations for everything, we don't even need to use header files. The result is a nice JSON document describing their classes and other entities like this:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0328ykp728ydnutra7r6.png" alt="macschema output"></p>
<p>From this we can start to automate the generation of wrapper types in macdriver, but as a standalone toolchain, the schemas it produces can be used to write bindings in languages other than Go as well.</p>
<p>Schemas like this can also be used in developer tooling for all sorts of things like autocompletion, or help create visual programming interfaces.</p>
<p>Today most &quot;no-code&quot; tools need to support integrations to stay competitive, but they're all writing and maintaining their own integrations. In fact, every application with user programmable integrations can benefit from schemas like this. So I'd like to see more.</p>
<p>If this is interesting, take a look at the tool I built, <a href="https://github.com/progrium/macschema">macschema</a>, which is open source on GitHub. Right now it's just the toolchain, but in theory we could generate schemas for all of Apple's APIs and put them in a repo in such a way that can be updated by a combination of macschema and user contributed patches. I'll let somebody else do that though.</p>
<p>Thanks for reading, think about schemas more, and consider <a href="https://github.com/sponsors/progrium">sponsoring my work</a>!</p>
]]></content:encoded></item><item><title>Customize your computer screen with HTML and JavaScript</title><link>https://progrium.xyz/articles/2021/customize-your-computer-screen-with-html</link><guid isPermaLink="true">https://progrium.xyz/articles/2021/customize-your-computer-screen-with-html</guid><pubDate>Tue, 30 Mar 2021 00:00:00 GMT</pubDate><description>A local webpage screen overlay for customizing your computing experience</description><content:encoded><![CDATA[<p>Anybody that knows how to mess around with HTML can now mess around with their desktop computing experience. <a href="https://github.com/progrium/topframe">Topframe</a> is an open source tool that lets you customize your desktop screen using HTML/CSS/JavaScript.</p>
<p>It started as a demo for <a href="https://github.com/progrium/macdriver">macdriver</a>, but over the last week or so I've been making a standalone version. 90% of that time was playing around with it while trying to make demos.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hbmx3sbht0l74w74ofud.gif" alt="Topframe Demo"></p>
<p>Could this bring back the computing aesthetic of personalized MySpace pages and Geocities sites all over again? No?? ...maybe? Well, either way, I'm happy to share a tool for people to <em>experiment</em> once again.</p>
<p><strong>Topframe is a fullscreen overlay that displays a special webpage served from your home directory.</strong> Editing the page source and hitting save will magically update your screen with nearly anything you can do in the browser.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v9pygiayoxsv3ci59bvk.gif" alt="Topframe Hello World"></p>
<p>Yes, it has <a href="https://tailwindcss.com/">Tailwind</a> built-in, as well as trusty old <a href="https://jquery.com/">jQuery</a>. However, you can drop anything you want in the topframe directory to use. Or hotlink, why not? This isn't WEBSCALE, it's just on your computer. Your <em>personal</em> computer.</p>
<h2>Getting Started with Topframe</h2>
<p>Topframe is still very early. The whole project is less than <a href="https://github.com/progrium/topframe/blob/main/topframe.go">400 lines</a> of Go. You'll also need a Mac, but it'll be cross-platform by 1.0.</p>
<p>Refer to the <a href="https://github.com/progrium/topframe#getting-started">README</a> for more details, but installing with <a href="https://brew.sh/">Homebrew</a> is as easy as:</p>
<pre><code>$ brew install progrium/taps/topframe
</code></pre>
<p>Then you can just run <code>topframe</code> and it'll run the overlay webpage with some crazy demo content. SOrry. The important thing to notice is the new menu bar icon 🔲 and menu:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kxed8nj4ktfynaff35mv.png" alt="Topframe Menu"></p>
<p>This is where you actually control Topframe, but there's not much to it. You can enable/disable (hide), make it interactive (by default mouse events just pass through), and quickly get to the source directory.</p>
<p>Clicking Show Source and opening the <code>index.html</code> file with your favorite editor, you can just wipe out all that garbage I put on your screen. HOWEVER, you can also look through it and see what you can find before blowing it away. There might be some cool ideas or features tucked away in there.</p>
<h2>What could this be used for?</h2>
<p>As the demo attempts to communicate, there is a LOT you can do with Topframe. For those streaming on Twitch (<a href="https://www.twitch.tv/progrium">like me</a>), you can use this as an alternative to doing overlays in OBS. Or, with some resourcefulness, you could make skins for all your windows. If anybody remembers the OS X Dashboard that they removed, you could make your own (better) version of that.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jvv2dlnt0jugia6whsli.png" alt="OS X Dashboard"></p>
<p>Or you could make some kind of screen Tamagotchi like Dogz from the 90s:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jj0wr32fwo4ob857qe8a.jpeg" alt="Dogz on Windows 3.1"></p>
<p>The only real limit is ... <a href="https://www.zombo.com/">yourself</a>.</p>
<h2>What Next</h2>
<p>Again, this is an early project, but it's also a mostly contributor-driven project. I'm going to keep focusing on tools to make stuff <em>like</em> this (including what'll make it cross-platform), so it'll be up to you all to really make this a real thing. For example, it could probably really use a global keyboard shortcut to toggle hiding or interactivity.</p>
<p>If you have questions, I'm watching the <a href="https://github.com/progrium/topframe/discussions">GitHub discussions</a> so feel free to post there. Otherwise, have fun and consider <a href="https://github.com/sponsors/progrium">sponsoring my work</a>!</p>
]]></content:encoded></item><item><title>Use Mac APIs and build Mac apps with Go</title><link>https://progrium.xyz/articles/2021/use-mac-apis-and-build-mac-apps-with-go</link><guid isPermaLink="true">https://progrium.xyz/articles/2021/use-mac-apis-and-build-mac-apps-with-go</guid><pubDate>Tue, 02 Feb 2021 00:00:00 GMT</pubDate><description>An Objective-C bridge for Go with bindings to common Mac APIs</description><content:encoded><![CDATA[<p>If you work with Apple devices and you're a Go programmer, or are thinking about learning Go, we just got some new powers that are pretty cool. Today I'm releasing an alpha of <a href="https://github.com/progrium/macdriver">macdriver</a>, an Objective-C bridge for Go with bindings to common Mac APIs.</p>
<p><a href="https://github.com/progrium/macdriver"><img src="https://github.com/progrium/macdriver/raw/main/macdriver.gif" alt="macdriver logo"></a></p>
<p>Those that were paying attention to my last post about <a href="https://dev.to/progrium/large-type-legacy-celebrated-in-80-line-go-program-1mob">Large Type in 80 lines of Go</a> might have already found macdriver. The largetype program is one of the example projects for macdriver.</p>
<p><img src="https://camo.githubusercontent.com/707db8e6d47c31ed90f0a65aeea1b805c718b1c18a2cd61b94e1ebb932b091af/68747470733a2f2f7062732e7477696d672e636f6d2f6d656469612f4571616f4f324d584941454a4e4b323f666f726d61743d6a7067266e616d653d6c61726765" alt="largetype screenshot"></p>
<p>Another example in the macdriver repo is this little menu bar extra (aka systray or status icon) that gives you a Pomodoro timer. This little program is <em>also</em> <a href="https://github.com/progrium/macdriver/blob/main/examples/pomodoro/main.go">less than 80 lines of Go</a> using macdriver.</p>
<p><img src="https://github.com/progrium/macdriver/raw/main/examples/pomodoro/pomodoro.gif?raw=true" alt="pomodoro screenshot"></p>
<p>Lastly, if you've been playing with the Go 1.16 beta, or can wait for its imminent release, there is also an example that gives you a full screen webview with a transparent background, letting you draw overlays and such on your screen using web tech. Twitch streamers might be into that one. Pretty cool, yea?</p>
<h2>Years in the making</h2>
<p>The motivation for macdriver was just making applications in Go that use Mac system APIs like Cocoa for making GUI apps. That said, most of my work now uses web tech for user interfaces, a la Electron.</p>
<p>Last year I started an alternative Electron stack in Go using the <a href="https://github.com/webview/webview">webview</a> project, which uses your platform's native browser engine and makes a window with a webview. While absolutely simple and cross platform, the project has not been prioritizing any platform specific functionality.</p>
<p>On top of this, if you want to also make a little menu applet like the Pomodoro timer in the same program (not... uncommon), you can find another standalone cross platform Go library for systray stuff, but these one-off libraries often suffer from being incomplete APIs. More importantly they are not composable! You can't use the systray library with the webview library because they both presume to own the main Cocoa thread.</p>
<p>So as usual, I was frustrated there wasn't a broader thought out, more general solution. What do I do if I wanted to use some other Mac API? Wait for another incomplete, non-composable project? No, we build our own future here.</p>
<p>I've actually been working on this (and so many other things) for a few years. I even found an <a href="https://github.com/mkrautz/objc">Objective-C bridge for Go</a> by a Danish fellow named Mikkel from <em>2013</em>, except... it didn't work.</p>
<p>This whole thing <em>should</em> be straightforward. The <a href="https://developer.apple.com/documentation/objectivec/objective-c_runtime">Objective-C runtime</a> is actually a C library called libobjc. So in theory you should be able to use cgo to call libobjc and work with Objective-C objects from Go like any other C library.</p>
<p>Unfortunately, the method calling functions in libobjc, to be generic to any method call, are variadic (takes arbitrary number of arguments). Cgo doesn't support variadic function calls! So Mikkel did an <a href="https://github.com/mkrautz/variadic">assembly hack</a> to make nearly any variadic C function callable. Amazing! But since 2013, changes to the Go runtime broke this and he had long since moved on to other projects.</p>
<p>With some determination I got the variadic hack working again. I even forgot I posted about this <a href="https://dev.to/progrium/start-of-an-objective-c-bridge-in-go-ep9">on dev.to in 2018</a>. There were still more things to figure out, but I shelved it until early last year when I needed to solve this webview and systray composability problem. I brought in the abandoned objc bridge package, the variadic package I fixed, and started making native Go wrappers for some of the more common Mac APIs. The result was macdriver!</p>
<h2>Benefits</h2>
<p>Now you can make fast, machine native binaries in Go that do Mac things. And I don't just mean make MacOS apps. There are a lot of APIs that just open up new possibilities. For example, I'm excited to use the Core ML APIs for working with the Neural Engine on the new M1 devices.</p>
<p>Another benefit is just being able to quickly make Mac apps using the Go build toolchain. No more Xcode! Just <code>go build</code>.</p>
<h2>What Next</h2>
<p>I'm excited to see what people do with macdriver. There's lots more information on using it in <a href="https://github.com/progrium/macdriver">the README</a>, but I am still working on docs. At this point it should mostly be self explanatory, but feel free to ask questions in <a href="https://github.com/progrium/macdriver/discussions">the forum</a>.</p>
<p>If you're interested in exploring new possibilities with me, I've been meaning to try using macdriver for an Apple Watch app. There was an iOS proof of concept in the original bridge package by Mikkel, but I haven't tried it. Assuming it works (which may involve Xcode or other developer tools), it seems plausible it would also work for the Apple Watch or even... the Apple TV.</p>
<p>Thanks for reading, have fun, and <a href="https://github.com/sponsors/progrium">consider sponsoring my work</a>!</p>
]]></content:encoded></item><item><title>Large Type legacy celebrated in 80 line Go program</title><link>https://progrium.xyz/articles/2021/large-type-legacy-celebrated-in-80-line-go-program</link><guid isPermaLink="true">https://progrium.xyz/articles/2021/large-type-legacy-celebrated-in-80-line-go-program</guid><pubDate>Fri, 15 Jan 2021 00:00:00 GMT</pubDate><description>A tiny, standalone largetype command-line utility</description><content:encoded><![CDATA[<p>There is a little known feature in the MacOS Contacts app where if you right-click a phone number, you get a Large Type option. Large Type fills your screen with the phone number in the largest font size possible, allowing you or anybody else to easily see the number from far away. Nifty!</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/f33yege5cr9zjdl9wgnj.jpg" alt="Old OS X Large Type"></p>
<p>As far as I know, this feature has been in every version of OS X since its release back in 2000. Contacts was known as Address Book back then. The builtin calculator also had a Large Type view, but it seems it has since been removed. The feature might even exist in NeXTSTEP apps.</p>
<p>Presumably inspired by OS X Address Book, Large Type was also added to the legendary <a href="https://qsapp.com/">Quicksilver</a> app launcher from the mid-2000's. Now, using Quicksilver, you could quickly type or paste in <em>any</em> text and display it in Large Type. More nifty!</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/rquufwhmpocuca5ljk6a.png" alt="Quicksilver Large Type"></p>
<p>Since I was involved in the SHDH hacker parties, this feature got a lot of use as a way to share a piece of information across a noisy, crowded room. Many users of Quicksilver also had a shell script that would run AppleScript to get Quicksilver to display text from the command line. Now it could be scripted!</p>
<p>It became the most memorable feature of my Quicksilver experience.</p>
<p>Large Type has the same sort of charm as the <code>say</code> command. It's nifty, maybe unnecessary, but really simple, there when you need it, and is kind of just a cool thing to play around with and show off.</p>
<p>The feature almost died with Quicksilver. Unsurprisingly, the modern Quicksilver equivalent, Alfred, also has Large Type. Except. I don't really want Alfred or Quicksilver installed just for Large Type now. It would be nice to have a standalone utility!</p>
<p>Thanks to a project I'm sharing in a couple weeks, I basically achieved perfection for such a utility. A standalone <code>largetype</code> command, in a single native code binary smaller than 4MB. And in under 80 lines of Go code:</p>
<p><img src="https://camo.githubusercontent.com/707db8e6d47c31ed90f0a65aeea1b805c718b1c18a2cd61b94e1ebb932b091af/68747470733a2f2f7062732e7477696d672e636f6d2f6d656469612f4571616f4f324d584941454a4e4b323f666f726d61743d6a7067266e616d653d6c61726765" alt="largetype screenshot"></p>
<p>{% gist https://gist.github.com/progrium/cb967815b3ed21a77e65a4ad9b1dbdf6 file=largetype.go %}</p>
<h3>Building</h3>
<p><em>Note: For now, Apple Silicon users need to be <a href="https://gist.github.com/progrium/b286cd8c82ce0825b2eb3b0b3a0720a0">set up for x86 mode</a></em></p>
<p>First, <a href="https://golang.org/dl/">download Go</a> or <code>brew install go</code>. Then, put <code>largetype.go</code> in a directory called <code>largetype</code> and from there run:</p>
<pre><code>$ go mod init largetype
$ go build
</code></pre>
<p>This will make you a 4MB binary called <code>largetype</code>. Now you can run it:</p>
<pre><code>$ ./largetype &quot;Hello world&quot;
</code></pre>
]]></content:encoded></item><item><title>Making the world more programmable</title><link>https://progrium.xyz/articles/2021/making-the-world-more-programmable</link><guid isPermaLink="true">https://progrium.xyz/articles/2021/making-the-world-more-programmable</guid><pubDate>Mon, 04 Jan 2021 00:00:00 GMT</pubDate><description>A draft talk I made 5 years ago trying to communicate my mission</description><content:encoded><![CDATA[<p>I'm starting this devlog up again to talk about some of the work I've been doing quietly for the past few years. It will mostly be project reveals, which should be exciting.</p>
<p>To kick things off I wanted to set the tone a little. I haven't given any serious talks in a while, and this is one of the last ones I put together. It's actually a rehearsal recording that's been private for 5 years UNTIL NOW of me framing my work around this mission: making the world more programmable.</p>
<p>This was one attempt at a clear narrative for what I'm trying to achieve, but I didn't feel it was good enough. Five years is a good while, though, and looking back it is a decent introduction for what's to come.</p>
<p>If I named this talk today I might instead call it &quot;Making the future malleable&quot; or something.</p>
<iframe src="https://player.vimeo.com/video/159888358?h=6ac5c365c8" width="640" height="360" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>]]></content:encoded></item></channel></rss>