<?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: Tran Huu Dang</title>
    <description>The latest articles on DEV Community by Tran Huu Dang (@2hjaito).</description>
    <link>https://dev.to/2hjaito</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4221790%2Fe2fc0d4e-c2ac-4a50-874a-7149231ae62c.jpg</url>
      <title>DEV Community: Tran Huu Dang</title>
      <link>https://dev.to/2hjaito</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/2hjaito"/>
    <language>en</language>
    <item>
      <title>I didn't want a web panel on my VPS, so I built a desktop for it over plain SSH</title>
      <dc:creator>Tran Huu Dang</dc:creator>
      <pubDate>Mon, 02 Nov 2026 07:55:03 +0000</pubDate>
      <link>https://dev.to/2hjaito/i-didnt-want-a-web-panel-on-my-vps-so-i-built-a-desktop-for-it-over-plain-ssh-3idi</link>
      <guid>https://dev.to/2hjaito/i-didnt-want-a-web-panel-on-my-vps-so-i-built-a-desktop-for-it-over-plain-ssh-3idi</guid>
      <description>&lt;p&gt;I look after a few Linux servers at work. Every day it's the same loop: check a container, tail a log, fix an nginx config, see why the disk is filling up.&lt;/p&gt;

&lt;p&gt;Web panels like aaPanel make that easier, but they come with a cost I didn't want to pay: another port open to the internet, another login page for bots to hammer, and another piece of software running as root on a production box.&lt;/p&gt;

&lt;p&gt;Typing everything by hand in a terminal works, but it's slow when you just want to look at things.&lt;/p&gt;

&lt;p&gt;So I built Davish: a desktop app that manages servers entirely over SSH, with an interface that feels like an operating system: a menu bar, a dock, and windows for each tool.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/P4yWIjaFI_s" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Three rules I didn't want to break
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Open it and you're in.&lt;/strong&gt; Davish uses the SSH keys and &lt;code&gt;~/.ssh/config&lt;/code&gt; you already have (including &lt;code&gt;ProxyJump&lt;/code&gt; through a bastion). There's no account and no login screen. It reconnects to the server you used last.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Nothing on the server.&lt;/strong&gt; No agent, no daemon, no extra port. If the server runs &lt;code&gt;sshd&lt;/code&gt; with SFTP, Davish works. Everything else is ordinary commands: &lt;code&gt;/proc&lt;/code&gt;, &lt;code&gt;systemctl&lt;/code&gt;, &lt;code&gt;journalctl&lt;/code&gt;, &lt;code&gt;docker&lt;/code&gt;, &lt;code&gt;ss&lt;/code&gt;, &lt;code&gt;nginx -t&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. No surprises.&lt;/strong&gt; Anything destructive (stopping a service, deleting a file, removing a container, replacing a crontab) shows the &lt;em&gt;exact&lt;/em&gt; command before it runs, and a command log records everything Davish ran. When root is needed, Davish doesn't store your sudo password: it opens a terminal with the &lt;code&gt;sudo …&lt;/code&gt; command typed in and lets you press Enter.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's inside
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Terminal&lt;/strong&gt; with tabs and GPU rendering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Files&lt;/strong&gt;: Finder-style SFTP, drag and drop, copy between two servers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Editor&lt;/strong&gt;: atomic saves that keep mode and owner, backups for files under &lt;code&gt;/etc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt;: containers grouped by compose project, images and layers, volumes, logs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nginx&lt;/strong&gt;: sites, &lt;code&gt;nginx -t&lt;/code&gt; before every reload, TLS expiry alerts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Services, Logs, Cron&lt;/strong&gt;: systemd units, live journals, cron schedules in plain words&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ports &amp;amp; tunnels&lt;/strong&gt;: see what's listening, tunnel it to &lt;code&gt;127.0.0.1&lt;/code&gt;, or open it in a built-in browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: failed logins grouped by IP, ufw and fail2ban status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSH keys, Packages, Git, Disk&lt;/strong&gt;: &lt;code&gt;authorized_keys&lt;/code&gt;, pending security updates, repo status, what's filling the disk
Each server is its own workspace (&lt;code&gt;⌘1&lt;/code&gt;…&lt;code&gt;⌘9&lt;/code&gt;) that remembers its windows, and &lt;code&gt;⌘K&lt;/code&gt; opens a Spotlight-style search for apps, files, services and containers.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Davish is written in &lt;strong&gt;Go&lt;/strong&gt; with &lt;strong&gt;&lt;a href="https://wails.io" rel="noopener noreferrer"&gt;Wails&lt;/a&gt;&lt;/strong&gt;, and the UI is &lt;strong&gt;Svelte 5&lt;/strong&gt;. Wails renders the UI in the operating system's own webview instead of shipping a copy of Chromium, which keeps the app small.&lt;/p&gt;

&lt;p&gt;A few details I found interesting while building it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One SSH connection per server.&lt;/strong&gt; Terminal, SFTP, metrics and log tails all open channels on the same &lt;code&gt;ssh.Client&lt;/code&gt; instead of new connections. Keepalives detect a dead link and the whole chain (including bastion hops) reconnects with backoff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agentless metrics.&lt;/strong&gt; The monitor keeps one long-lived SSH session running a small POSIX &lt;code&gt;sh&lt;/code&gt; loop that prints &lt;code&gt;/proc/stat&lt;/code&gt;, &lt;code&gt;/proc/meminfo&lt;/code&gt;, &lt;code&gt;/proc/net/dev&lt;/code&gt; and &lt;code&gt;df&lt;/code&gt; every couple of seconds. Go parses the snapshots and computes deltas. When the window is hidden or the app is locked, it slows down to save resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't trust the login shell.&lt;/strong&gt; Some servers use zsh or fish, and some have a &lt;code&gt;.bashrc&lt;/code&gt; that hangs. Davish runs its own commands under &lt;code&gt;sh&lt;/code&gt; and gives every command a deadline, so one bad shell config can't freeze the UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terminal output in batches.&lt;/strong&gt; Emitting one event per read flooded the webview when you &lt;code&gt;cat&lt;/code&gt; a big file. Output is now batched in ~16 ms windows and sent as base64, since it isn't always valid UTF-8.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safe edits.&lt;/strong&gt; Files are written to a temp file in the same directory and renamed over the original, so a dropped connection never leaves a half-written config behind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secrets stay in the keychain.&lt;/strong&gt; Passwords and passphrases live only in the OS keychain. Host keys are checked on first connect, and a changed host key is refused outright.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Davish runs on macOS, Windows and Linux and is open source under GPLv3.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/2hjaito/davish" rel="noopener noreferrer"&gt;https://github.com/2hjaito/davish&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Downloads: &lt;a href="https://github.com/2hjaito/davish/releases" rel="noopener noreferrer"&gt;https://github.com/2hjaito/davish/releases&lt;/a&gt;
The builds aren't code-signed yet, so macOS and Windows will warn the first time. The README explains how to open it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;So far Davish is good at &lt;em&gt;running&lt;/em&gt; servers you already have. Next I'm working on &lt;em&gt;setting things up&lt;/em&gt;: new nginx sites with one-click SSL, containers and compose stacks from a form, cloning a repo and deploying it, and firewall plus backups.&lt;/p&gt;

&lt;p&gt;If you manage a VPS with a web panel today, I'd love to know: &lt;strong&gt;what would you need to drop it?&lt;/strong&gt; Comments and GitHub issues are both welcome.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>opensource</category>
      <category>go</category>
      <category>security</category>
    </item>
  </channel>
</rss>
