<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vinicius Apolinário</title>
    <description>The latest articles on DEV Community by Vinicius Apolinário (@viniciusap).</description>
    <link>https://dev.to/viniciusap</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3970875%2F58a610fd-c43e-4cd7-bfb5-b63224145129.jpeg</url>
      <title>DEV Community: Vinicius Apolinário</title>
      <link>https://dev.to/viniciusap</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/viniciusap"/>
    <language>en</language>
    <item>
      <title>I got tired of AnyDesk and TeamViewer so I built my own remote desktop</title>
      <dc:creator>Vinicius Apolinário</dc:creator>
      <pubDate>Sat, 06 Jun 2026 18:58:15 +0000</pubDate>
      <link>https://dev.to/viniciusap/i-got-tired-of-anydesk-and-teamviewer-so-i-built-my-own-remote-desktop-12hk</link>
      <guid>https://dev.to/viniciusap/i-got-tired-of-anydesk-and-teamviewer-so-i-built-my-own-remote-desktop-12hk</guid>
      <description>&lt;p&gt;For a while I'd wanted a way to control my home machines without depending on AnyDesk, which now asks for an account just to connect, or TeamViewer, which kept deciding my personal use was "commercial" and locking me out. Nothing out there quite fit what I wanted, so over the last few months I built &lt;strong&gt;SelfDesk&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Repo (MIT): &lt;a href="https://github.com/Viniciusap/selfdesk" rel="noopener noreferrer"&gt;https://github.com/Viniciusap/selfdesk&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpy03d3n7msav1tjjfr3j.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpy03d3n7msav1tjjfr3j.gif" alt="SelfDesk demo" width="760" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The idea is pretty simple. You run a small broker on any Linux box and your Windows machines connect outbound to it. A Raspberry Pi handles it without trouble. Nothing listens for inbound connections, there's no third-party cloud in the middle, and nothing phones home. It all stays on your own network.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SENDER (C#/.NET)            BROKER (Node.js)          RECEIVER (C#/WPF)
 capture + encode  ──TLS──▶  authenticated relay  ◀──TLS──  renders + input
 inject input               (never decodes video)           picks the sender
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The broker is basically a dumb authenticated pipe. It routes bytes by &lt;code&gt;peer_id&lt;/code&gt; and never decodes the video, so adding a second machine is pure config, no code changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;p&gt;This was the part I cared about most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TLS 1.3 everywhere, with a LAN-local CA. No public CA, no plaintext fallback.&lt;/li&gt;
&lt;li&gt;HMAC-SHA256 challenge-response auth, so the shared secret never actually crosses the wire.&lt;/li&gt;
&lt;li&gt;Zero inbound ports on the Windows machines. Outbound only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's built for trusted LANs, not direct internet exposure. If you need access from outside, put the broker behind a VPN or a tunnel.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does today
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Screen capture and full remote control (mouse, keyboard, scroll)&lt;/li&gt;
&lt;li&gt;Clipboard sync both ways&lt;/li&gt;
&lt;li&gt;Drag-and-drop file transfer with a progress bar&lt;/li&gt;
&lt;li&gt;Wake-on-LAN straight from the viewer&lt;/li&gt;
&lt;li&gt;Hardware H.264 (Quick Sync or NVENC) if you have an Intel or Nvidia GPU&lt;/li&gt;
&lt;li&gt;Several machines from one viewer, switch between them with a click&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Setup is two steps per machine: the install one-liner pulls and unpacks the latest release, then a bootstrap generates the &lt;code&gt;.env&lt;/code&gt;, the &lt;code&gt;SHARED_SECRET&lt;/code&gt; and the TLS certificates. That same one-liner is also the updater — run it again any time and it grabs the newest build while preserving your existing &lt;code&gt;.env&lt;/code&gt; and certs. On a fresh machine it doesn't start anything on its own; it prints the bootstrap step for you to run. Do the broker first, since it mints the secret and the CA the others pin to, and copy its &lt;code&gt;ca-cert.pem&lt;/code&gt; onto the sender and viewer before bootstrapping them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Broker&lt;/strong&gt; — Linux, WSL or Git Bash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Viniciusap/selfdesk/master/scripts/install-broker.sh | bash
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/selfdesk
./scripts/bootstrap.sh broker
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow from &amp;lt;YOUR_SUBNET&amp;gt;/24 to any port 7000 proto tcp
node dist/index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a pure Windows broker (no bash), use the PowerShell installer instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;irm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://raw.githubusercontent.com/Viniciusap/selfdesk/master/scripts/install-broker.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sender&lt;/strong&gt; — the machine you want to control (Windows, PowerShell):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;irm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://raw.githubusercontent.com/Viniciusap/selfdesk/master/scripts/install-sender.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Viewer&lt;/strong&gt; — the machine you sit at (Windows, PowerShell):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;irm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://raw.githubusercontent.com/Viniciusap/selfdesk/master/scripts/install-viewer.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bootstrap walks you through each value and writes the &lt;code&gt;.env&lt;/code&gt; files, so you don't normally edit them by hand. Here's what each one ends up holding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# broker/.env
&lt;/span&gt;&lt;span class="py"&gt;ROLE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;broker&lt;/span&gt;
&lt;span class="py"&gt;SHARED_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;generated here — paste into the other two&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;LISTEN_PORT&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;7000&lt;/span&gt;
&lt;span class="py"&gt;ALLOWED_SENDERS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;laptop-01,laptop-02&lt;/span&gt;
&lt;span class="py"&gt;TLS_CERT_PATH&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;certs/server-cert.pem&lt;/span&gt;
&lt;span class="py"&gt;TLS_KEY_PATH&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;certs/server-key.pem&lt;/span&gt;
&lt;span class="py"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;info&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# sender/.env
&lt;/span&gt;&lt;span class="py"&gt;ROLE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sender&lt;/span&gt;
&lt;span class="py"&gt;SHARED_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;same as broker&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;SENDER_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;laptop-01          # must be listed in ALLOWED_SENDERS&lt;/span&gt;
&lt;span class="py"&gt;BROKER_HOST&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;192.168.1.10&lt;/span&gt;
&lt;span class="py"&gt;BROKER_PORT&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;7000&lt;/span&gt;
&lt;span class="py"&gt;TLS_CA_PATH&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;certs/ca-cert.pem&lt;/span&gt;
&lt;span class="py"&gt;ENCODER&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;jpeg                 # or qsv (Intel) / nvenc (Nvidia)&lt;/span&gt;
&lt;span class="py"&gt;TARGET_FPS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;30&lt;/span&gt;
&lt;span class="py"&gt;JPEG_QUALITY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;75&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# viewer/.env
&lt;/span&gt;&lt;span class="py"&gt;ROLE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;receiver&lt;/span&gt;
&lt;span class="py"&gt;SHARED_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;same as broker&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;BROKER_HOST&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;192.168.1.10&lt;/span&gt;
&lt;span class="py"&gt;BROKER_PORT&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;7000&lt;/span&gt;
&lt;span class="py"&gt;TLS_CA_PATH&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;certs/ca-cert.pem&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only two rules to keep in mind: &lt;code&gt;SHARED_SECRET&lt;/code&gt; has to be identical on all three machines, and each &lt;code&gt;SENDER_ID&lt;/code&gt; has to match an entry in the broker's &lt;code&gt;ALLOWED_SENDERS&lt;/code&gt;. Building from source instead of binaries is documented in the README.&lt;/p&gt;

&lt;h2&gt;
  
  
  I could use some help
&lt;/h2&gt;

&lt;p&gt;It's only been public for about a week, so it's early. The core runs well on my own setup, but I'm sure there are rough edges I haven't hit yet, and right now it's just me working on it. There's plenty on the list: more testing across different hardware and GPUs, codec work, docs, packaging, and one of the bigger things I want to tackle next is macOS support, since today the sender and viewer are Windows-only.&lt;/p&gt;

&lt;p&gt;If self-hosting, or lower-level .NET and networking, is your thing, I'd really appreciate contributors. The codebase, comments, tests and docs are all in English now, so it's approachable to dig into. I've opened a few &lt;a href="https://github.com/Viniciusap/selfdesk/issues" rel="noopener noreferrer"&gt;issues to get started&lt;/a&gt; — there's a &lt;a href="https://github.com/Viniciusap/selfdesk/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22" rel="noopener noreferrer"&gt;good first issue&lt;/a&gt; if you want something small, and the bigger pieces like a unified setup command and macOS support are tagged &lt;a href="https://github.com/Viniciusap/selfdesk/issues?q=is%3Aissue%20state%3Aopen%20label%3Aenhancement" rel="noopener noreferrer"&gt;&lt;code&gt;Enhancement&lt;/code&gt;&lt;/a&gt;. PRs and feedback on the setup experience are just as welcome.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/Viniciusap/selfdesk" rel="noopener noreferrer"&gt;https://github.com/Viniciusap/selfdesk&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>selfhosted</category>
      <category>dotnet</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
