A macOS desktop app for managing your /etc/hosts file and ~/.ssh/config. Built with Electron, React, and TypeScript.
HostsLab provides a visual interface to manage host entries and SSH connections without manually editing config files. Two tabs give you access to both tools from a single app.
Features
Hosts Tab
- View all entries from
/etc/hostsin a table - Add new host mappings
- Edit existing entries (IP and hostname)
- Toggle entries on/off (comments/uncomments the line)
- Delete entries
- System entries are protected from accidental modification
- Preserves comments, blank lines, and formatting in the hosts file
- Backs up the original hosts file before the first modification
- Elevated permissions via native macOS password prompt
SSH Config Tab
- View all host entries from
~/.ssh/configin a table - Add, edit, and delete SSH connection entries
- Fields: Host (alias), HostName (IP/domain), User, Port, IdentityFile
- Dropdown shows detected SSH keys from
~/.ssh/ - Create new SSH keys (Ed25519, RSA 4096, ECDSA) directly from the app
- No sudo required — user owns
~/.ssh/config - Creates
~/.ssh/directory and config file if they don't exist - Sets proper file permissions (700 for directory, 600 for config and private keys)
- Backs up original config on first edit
- Preserves comments, unknown directives, and wildcard Host blocks
How It Works
Architecture
The app follows Electron's process model with a clear separation between main and renderer:
Renderer (React UI)
-- IPC bridge -->
Main Process (Node.js)
-- reads/writes -->
/etc/hosts (via sudo)
~/.ssh/config (direct)
~/.ssh/* (key scan + ssh-keygen)
- Renderer process only sees typed objects (
HostEntry[],SSHEntry[]) — it never touches raw files - Main process parses config files line-by-line, caching the full structure to preserve comments, blank lines, and formatting between saves
- IPC channels:
hosts:read— returnsHostEntry[]hosts:save(entries)— writes to/etc/hostsvia elevatedcp, returns{success, error?}ssh:read— returnsSSHEntry[]ssh:save(entries)— writes to~/.ssh/configdirectly, returns{success, error?}ssh:listKeys— scans~/.ssh/for private keys, returnsSSHKey[]ssh:createKey(options)— runsssh-keygen, returns{success, keyPath?}
Hosts File Parsing
- Blank lines and
#comments are preserved as-is - Lines like
# 127.0.0.1 example.comare treated as disabled entries - Multi-hostname lines (e.g.
127.0.0.1 foo bar) are split into separate entries - System entries (
localhost,broadcasthost,::1 localhost) are marked read-only
SSH Config Parsing
- Parses
Hostblocks with indented key-value directives - Recognized directives: HostName, User, Port, IdentityFile
- Wildcard hosts (
Host *) are preserved in the file but not shown as editable entries - Unknown directives and comments are preserved on save
Saving
Hosts file:
- Serializes entries by walking the cached line structure
- Writes to a temp file
- Uses
osascriptwithdo shell script ... with administrator privilegesto copy the temp file to/etc/hostswith proper permissions - Re-reads the file to keep the cache in sync
- A backup of the original file is saved to the app's user data directory on first save
SSH config:
- Serializes entries back into SSH config format (Host blocks with indented directives)
- Writes directly to
~/.ssh/configwith mode 600 - Re-reads the file to keep the cache in sync
- A backup is saved to the app's user data directory on first save
SSH Key Detection
- Scans
~/.ssh/for private key files on app load - Detects known key types:
id_rsa,id_ed25519,id_ecdsa,*.pem - Also detects any file with an
OPENSSH PRIVATE KEYorPRIVATE KEYheader - Skips
.pubfiles,known_hosts,config, andauthorized_keys
SSH Key Creation
- Generates keys via
ssh-keygenwith no passphrase - Supports Ed25519 (recommended), RSA 4096, and ECDSA
- Sets permissions: 600 for private key, 644 for public key
- Refreshes the key list automatically after creation
Project Structure
src/
types.ts # Shared types (HostEntry, SSHEntry, SSHKey, ElectronAPI, etc.)
index.ts # Main process entry — creates window, registers IPC
preload.ts # Exposes electronAPI via contextBridge
renderer.ts # Renderer entry — loads CSS and app
app.tsx # React root mount
index.html # HTML shell
main/
hosts-file.ts # Parse, serialize, backup, save hosts file
ssh-config.ts # Parse, serialize, backup, save SSH config + key management
ipc-handlers.ts # ipcMain.handle for all IPC channels
components/
App.tsx # Top-level layout, tab navigation, state orchestration
HostsTable.tsx # Hosts entries table
HostRow.tsx # Single host row with toggle/edit/delete
EntryForm.tsx # Hosts add/edit modal form with validation
SSHTable.tsx # SSH entries table
SSHRow.tsx # Single SSH row with edit/delete
SSHEntryForm.tsx # SSH add/edit modal form with key dropdown
SSHKeyModal.tsx # Create new SSH key modal
Toast.tsx # Toast notifications
EmptyState.tsx # Empty state message
hooks/
useHosts.ts # Hosts CRUD state management + IPC calls
useSSH.ts # SSH CRUD state management + key operations + IPC calls
useToast.ts # Toast state management
styles/
app.css # All app styles
Development
Prerequisites
- Node.js (v18+)
- npm
- macOS (the elevated save uses
osascript)
Setup
Run
This launches the app in development mode with hot reload for the renderer process. Changes to main process files require a restart.
Build
Produces a packaged .app in the out/ directory.
Creates distributable installers (.dmg and .zip on macOS). Builds for both Intel (x64) and Apple Silicon (arm64).
Lint
Releases
Releases are built automatically via GitHub Actions. To create a new release:
git tag v1.0.0 git push origin v1.0.0
This triggers a workflow that:
- Builds the app on macOS for both Intel (x64) and Apple Silicon (arm64)
- Signs and notarizes the app with Apple via App Store Connect API key
- Produces
.dmgand.zipartifacts for each architecture - Creates a GitHub Release with auto-generated release notes and all downloadable artifacts
Download the latest release from the Releases page.
License
MIT
