words with kitchen

real life adventures of an aspiring adult

iPad as Desktop: Emacs

Lately I’ve been working on a setup such that I can use my iPad as my primary desktop, at least for personal tasks. In this, the first of what I plan as a series of posts about my journey, I’ll outline what I’m currently using to be able to use emacs from my iPad.


For the impatient, and also to serve as an outline for the post, I’ll just list out the tools I’m using to make this happen.

  • Blinkshell
  • Tailscale
  • Mosh
  • An M1 Mac Mini
  • A hardware keyboard

The combination of the above gives me a nearly desktop-native experience running emacs and other terminal applications on my iPad. Note that this setup of course requires network connectivity between the iPad and the Mac Mini, and there are some complications with that, but I’ll try to cover those later in the post.

The finished product: emacs running on a Mac Mini that I’m using Blinkshell to connect to with mosh over a Tailscale VPN connection


I run emacs as my primary editor/IDE/operating system. I’m not one of those old school emacs users, I’m one of the new young punks using someone else’s emacs. In my case, I’m using the doom-emacs package, which provides me with a very comfortable user experience pretty much out of the gate, and since I’ve been a vim user for so long, the adjustment wasn’t that significant.

Emacs is a gui application framework wearing the skin of a text editor. It has support for both terminal ui (tui) or actual native gui (gui) interfaces, and can do so much more than editing text. I primarily use emacs as an editor, however, and I primarily use emacs over other editors because of org-mode and org-roam. I’ll talk about org-roam in another post.

Emacs doesn’t have a native iOS port and it is highly unlikely that it ever will. Largely in part due to application restrictions on iOS making code interpretation not a supported thing, which is fundamental not only to the emacs experience, but fundamental to emacs operation as a whole. I mentioned that emacs is a gui application framework, and it is, but as part of that framework it has its own programming language, emacs lisp, which powers not only custom things, but a lot of emacs’ own built in functionality.

So, as a result, I need a machine somewhere I can run emacs on and have it display on my iPad. And the best experience for that is, in my opinion, a terminal.


Blinkshell is a terminal emulator, ssh, and mosh client for iOS. There’s so much more to it than that, but it has the basic functionality I need: terminal emulation, ability to configure ssh key authentication, ability to do ssh agent forwarding (I won’t be covering this here, as I’m on the lookout for alternatives and it’s been covered elsewhere). It also provides an extremely basic text interface for commands, which is nice, I primarily live in the command line and on my keyboard, not having to tap my way through menus and such to do things is a huge plus.

It’s not a free app, but I’m totally fine with paying for good software. It *is* open source, which is nice, as that allows for auditability and also allows me to try to fix bugs or implement features if I feel up to the challenge.

One thing I discovered recently that it had was support for OSC52 terminal sequences. This is something I only recently discovered, but the general gist of it is that it allows terminal applications to manipulate the clipboard on the client system. If you’ve ever highlighted text on iOS you understand just how hard it can be. Now take things like terminal gui elements into consideration, and it’s basically impossible to copy/paste from the terminal by hand. However, OSC52 allows your terminal application (such as emacs, vim, tmux, etc) to send its own clipboard contents down, so you can use those applications’ native copy/paste functionality to manipulate the clipboard on iOS, making it easy to, say, copy some code from a file and paste it into a discord conversation, or a gist. This is huge, and one of the things that when I first needed to copy something from blink was nearly a deal breaker for me, as I copy things out of my editor all the time, so having that be a bad experience was probably going to prevent me from being able to do this. Fortunately, the problem is mostly solved at this point. There are still some limitations, for instance it doesn’t seem to work with mosh, but there’s a PR waiting to be merged into upstream mosh which hopefully will make its way into blink soon after.


Traditionally, when I’ve wanted to grant access to a machine running on my home network, I have fired up my router configuration web interface, set a static IP for the machine, forwarded a port, etc. This works fine, but has some serious drawbacks. The most important one is that now this port is exposed publicly on the internet. That’s probably not toooooooo bad, especially if I run the service on an alternate port, but it’s still a vulnerability. It’s also a huge pain for the friend who’s hosting the machine for me, as they have to maintain this. And the only way they know if it’s broken is if I call them. Furthermore, it assumes a moderately static IP for the internet connection. When I had my cable connection, this was pretty reasonable to assume, my IP rarely changed. But with my new fiber connection, it seems to change fairly frequently. To solve that I have DNS somewhere and a script to update the dns record on a schedule, but if that breaks then I’m back to calling my friend. It’s all very fragile and troublesome to fix.

So I went on the search for vpn solutions. I thought of having a VM instance somewhere that both my remote shell machine and my iPad could connect to and that would allow me to connect between them. This is fine, and something I almost went with. I would have likely used OpenVPN for this as I’ve done a lot of stuff with OpenVPN in the past, I’m familiar with it, and confident that I could make it do what I wanted. One problem with this, though, is that third party machine. Even the cheapest machine is going to cost me a few bucks a month, and now it’s something I have to maintain. There are third party VPN providers, but I’m not sure how many of them would allow my clients to connect to each other through the VPN, and I’d really rather not use one of those providers anyways, for trust reasons, but also because I really don’t like their ad copy. But that’s another post.

But! I remembered. There’s some new vpn thing built into the linux kernel now, isn’t there? What was that called? Well, it was called Wireguard. I also won’t cover this here because it’s been covered in great detail elsewhere.

Wireguard’s model is point to point VPN. Meaning clients create tunnels directly to each other. Once the connection is established, you can do routing and all sorts of stuff, so I could do the central model with a VM somewhere with wireguard, but it’s still not ideal.

So I went looking for ways to make it so wireguard endpoints could “discover” each other. So I could have my shell machine on a private network with no ports opened, no assumption of a dynamic IP address, etc, and have it just work. I found this article that talks about various scripts and a third machine that updates dns entries and does some cool stuff. Exactly the sort of thing I was looking for. But again, the third box. I thought more about it and thought that would actually make for an interesting service to offer to the world, and thought about how I might implement that.

It seems I’m not the only one who had this idea. Tailscale is *exactly* this. And actually, it’s significantly more. Most importantly for my consideration is that it takes NAT traversal a step further. They will transparently (and securely) proxy your data between your nodes if the NAT needing to be traversed is particularly problematic. On either end. This means that, in theory, no matter where I am, or where my shell box is hosted, I can establish a wireguard tunnel to it.

And the best part: it’s free. At least for single user purposes like mine. They can do this because their service is just providing the discovery for the endpoints, they aren’t actually proxying your data (unless you have the aforementioned troublesome NAT), so their bandwidth needs are relatively low. You install the agent on your machine (they have iOS, Linux, and Mac support at the very least), log in, and your machine is now able to be connected to from the other peers you have set up, as if it’s all local. No ports to open, no dns to configure, no worries about static or dynamic IPs. It all just handles everything smoothly. Wireguard handles when one end or the other changes IP addresses, and tailscale steps in if both sides have changed, or when a new peer comes online and doesn’t know any of its peers. It also handles public key distribution to the peer nodes, and rotation of keys, and and and and and. Seriously well thought out software and service.


Blinkshell support ssh directly, but something else it supports is Mosh, the Mobile Oriented SHell. Mosh has some significant advantages over standard ssh that make it ideal for a client with transient internet connectivity such as a traveling iPad.

Mosh uses SSH for the initial authentication and session establishment, then switches to running on its own port using UDP. The fact that it uses UDP for this is huge, because it allows the client to go offline without having to worry about a TCP session dropping. This means mosh clients can go offline for a practically infinite amount of time without “losing the connection”. In practice what this means is I can pack up my iPad and head out on a trail for a few days without internet access, fire things up when I get back and my connection hasn’t dropped. This may not sound like a big deal, especially when things like tmux exist to allow you to background a terminal session on the remote machine, but it’s pretty big. For the iPad specifically one of the great things about this is that iPadOS times out tcp connections on background processes pretty aggressively to save battery life. So if I switch from my shell to watch a youtube video, say, or read an article, I may come back to my terminal and the connection has dropped, meaning I need to re-establish. This gets really old really quickly, as you might imagine. One extra cool bonus feature of this is, since mosh sessions are inherently resumable, so long as the client keeps the connection information around, a connection can be resumed even after rebooting the client for a software update. Something I recently did! I was pretty blown away!

One of the best features of mosh for actual mobile usage, though, is its predictive local echo. If you’ve ever been ssh’d into a remote machine with a lot of latency, you know just how frustrating an experience typing can be. Mosh makes a great attempt at solving this by guessing what affect your keystrokes are going to have on the remote session and applies those affects locally before getting the result back from the remote system. It’s obviously not perfect at it, it can’t be, but for a lot of cases, it’s a HUGE enhancement to the mobile experience. This will be extremely useful for instances such as camping on a remote mountain top in Japan using my LTE connection to connect to my shell box on a cable connection in Portland. Something that absolutely will happen.

A downside to mosh is it doesn’t support ssh agent forwarding. SSH agent forwarding is a troublesome topic. It’s one of those things you really want to have, but it’s implemented in such a way that you really shouldn’t use it. There are alternatives, but they aren’t widespread, and they aren’t currently supported by blinksh. I think my solution for this in the long term is to have an ssh agent start on login on my shell box, then I’ll enter my ssh passphrase on first use to add it to the agent. Since the client would be running with mosh anyways, I shouldn’t have to type my password very often, and it should provide adequate protection for my private key.

Mac Mini

Throughout this I’ve talked a lot about my “remote shell box”. In my case, I’m choosing to go with a Mac Mini. Currently I’m using an M1 Mac Mini with 16GB of ram and 512GB of storage. This should be completely adequate me for a very long time, especially when just using it as a shell box.

But why the Mac mini? Why not a raspberry pi? Or a linux box? Maybe even a linux box hosted in the cloud somewhere so I don’t have to worry about VPNs and firewalls and port forwarding and all of that.

There are a couple of reasons. First and foremost, cost. A virtual machine that has those sorts of specs is quite an expensive proposition. I mean, the absolute cost of the Mac mini being factored in means I can buy a lot of VM hours before reaching the cost of the Mini, especially if I do some optimizations like turning the VM off when I’m not using it, etc. So it’s only a moderately important consideration for me.

Another major consideration for me is storage. I currently use iCloud Drive for my cloud storage stuff. I use this primarily because I’ve leaned very hard into the Apple ecosystem, so all of my devices have native support for it. I also pay for other Apple services, so the storage I use with iCloud Drive is pretty much free. I could use dropbox or something else, or even something like syncthing and roll it myself, but I like the safety net of a cloud backed storage mechanism, and my cloud backed storage mechanism of choice is iCloud Drive.

Furthermore, despite being out for a long time, despite “mobile devices” being a lot of folks’ primary experience with the web, there are lots of web interfaces that just don’t work well on the iPad, be it in Safari (my browser of choice) or Chrome. Oddly, one of the biggest offenders is the AWS console. It is literally unusable on safari for some functions, and only just works on Chrome for some things. And I’m not talking about the obscure things nobody uses, I’m talking about first class fundamental features like Route53 or S3. So having easy access (via VNC) to a full desktop web browser is very handy. This is something I could certainly do through other means, but having it be on the Mac Mini means I get things like my 1password database for free without having to load it onto some third party machine (and good luck trying to do U2F via some sort of remote desktop connection, which I need for my 1password authentication)

And finally, there’s part of me that still would really like to figure out iOS app development. I have some ideas for things I’d like to implement, and at the moment, iOS has no native support for XCode. I can’t imagine it’s not in the works somehow, as it would be great to be able to write software for the iPad, y’know, on the iPad, but there’s probably going to be some cloud component to it for compilation and simulation before that can happen, and it’s just one of many projects Apple has in flight. So having a Mac desktop I can VNC into and do some iOS development on while I’m on the road is great.

Hardware keyboard

Not just because I’ve gotten into custom mechanical keyboards during the pandemic, in order to use my iPad as a desktop, I need a hardware keyboard. There’s just no way at all I can type on the iPad screen. Not even a little bit. I can do some mild hunt and peck if my keyboard isn’t handy or if I’m mostly browsing the internet or youtube and need to type in a url or a search term, but for the most part, I need a physical keyboard.

There are many options for this, but I did say I’ve gotten into custom mechanical keyboards lately, so I’m currently using a caseless Corne Chocolate v2.1 keyboard with a pair of nice!nano controllers for wireless bluetooth connectivity. I made it into the group by for the Corne-ish Zen keyboard and am probably going to make that my primary travel board next, and am looking into building some other types of boards to see if I like one or the other better for travel purposes. I’ll probably talk about my keyboard use cases in another post, but I also distinguish between keyboards for “long term travel” (backpacking and traveling mostly on foot) and “short term travel” or “heavy travel” (traveling mostly by not-foot, such as work travel or “heading out to the coast for the weekend” sort of travel)


That’s a whole lot of words, and if you’re reading this, thanks for reading along. I have other things I want to cover about this journey toward using an iPad as a primary desktop, there will be some things that are “this is the definitive way to do this” and stuff like “I’m trying a new thing and wanted to share”. I am enjoying this little experiment so far, and getting more and more confident that I’ll be able to use just my iPad for my future planned “long term travel” (more about that at some point as well).

One thing I do want to say is that it’s unlikely that I’ll be able to use just an iPad for work purposes any time soon. I’m still very much bound to a shell of some sort, and then there’s consideration of device and credential management from employer, consideration of ergonomics of using effectively a tiny laptop display for all of my work, etc, but I would like to eventually give it a solid try! Maybe in a few years once Apple has further converged the iPadOS and MacOS experiences.

Bonus action shot of me working on this post at East Side Coffee

3 responses to “iPad as Desktop: Emacs”

  1. Thank you very much for sharing all this information. I am using a similar setup. There are so many little tips and tricks in your post so I can fine tune and fix some of the hurdles. Especially


  2. Nice work~! Your setup is as exactly same as mine~ but mosh sometimes lag and no responded when re-connection. I’ll look into tailscale next~


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: