A modern desktop file transfer application for AWS S3, SFTP, network filesystems, object storage, and Tailscale Taildrop, built with Electron, React, and a custom dark UI theme.
Aether provides a dual-pane file manager with drag-and-drop transfers, multiple simultaneous connections, recursive directory operations, and encrypted credential storage — all wrapped in a warm indigo-tinted interface designed to feel atmospheric rather than flat.
- AWS S3 — browse buckets, upload/download objects, create folders, batch delete with retry
- SFTP — password or SSH key authentication, full remote filesystem browsing
- Network filesystems — connect to SMB, NFS, WebDAV, FTP/FTPS, and rsync targets
- Object storage — connect to Azure Blob Storage and Google Cloud Storage alongside AWS S3
- Tailscale Taildrop — send local files to eligible tailnet devices from a built-in destination pane
- Drag and drop between panels or from the OS file manager
- Recursive directory transfers — directories are automatically expanded into individual file transfers
- Concurrent transfers — up to 3 simultaneous transfers via a managed queue (p-queue)
- Progress tracking — real-time speed, bytes transferred, animated progress bars
- Automatic retry — failed transfers retry up to 3 times with exponential backoff
- Tailscale is a built-in destination mode, not a saved connection profile.
- Aether uses the local
tailscaleCLI and the user's existing Tailscale session; no Tailscale credentials are stored. - The right pane lists Taildrop targets from
tailscale file cp --targets, including offline device states. - Sending uses
tailscale file cp <file> <target>:and records file-level status in the transfer/history area. - On Linux, received files can be collected into the current local folder with
tailscale file get <folder>. - On macOS and Windows, received files are handled by the Tailscale desktop client/OS Downloads flow.
- Taildrop is documented by Tailscale as alpha; unsupported, disabled, or unavailable states are surfaced in the UI.
- Save and manage multiple S3, SFTP, SMB, NFS, WebDAV, FTP/FTPS, rsync, Azure Blob Storage, and Google Cloud Storage profiles
- S3 auth methods: access keys, IAM role assumption, named AWS profile, default credential chain
- SFTP auth methods: password, SSH private key (with optional passphrase)
- Network connection options: mounted share paths for SMB/NFS/WebDAV, password-based FTP/FTPS, and SSH-backed rsync
- Object storage options: Azure account keys/SAS tokens and GCS service account keys
- Credentials encrypted at rest via Electron safeStorage
- Test connection before saving
- Local panel (left) — browse the local filesystem
- Remote panel (right) — browse S3 buckets/objects, SFTP servers, SMB/NFS/WebDAV shares, FTP/FTPS/rsync targets, Azure Blob containers, and GCS buckets
- Sortable columns (name, size, modified date)
- Keyboard shortcuts: Delete, Ctrl+A (select all), Ctrl+R (refresh), Ctrl+N (new folder), F2 (rename), Escape (clear selection)
- Shift-click range selection and Ctrl-click multi-selection
- Right-click context menu with all file operations
- Color-coded file type icons
- Custom frameless title bar with window controls
- Collapsible sidebar with quick-access directories and connection list
- Resizable panels (horizontal split) and drag-to-resize transfer queue
- Animated drop zones with glowing borders
- Toast notifications for errors and actions
- Dark theme with OKLCH color system (Geist Sans + Geist Mono typography)
| Layer | Technology |
|---|---|
| Framework | Electron 40 + Electron Forge + Vite 7 |
| UI | React 19, Tailwind CSS v4, shadcn/ui (new-york), Radix UI |
| Animation | framer-motion, CSS keyframes |
| State | Zustand (5 stores) |
| S3 | @aws-sdk/client-s3, @aws-sdk/lib-storage, @aws-sdk/credential-providers |
| SFTP | ssh2-sftp-client |
| Network Filesystems | Local mounts, FTP/FTPS, rsync |
| Object Storage | Azure Blob Storage, Google Cloud Storage |
| Taildrop | Local Tailscale CLI |
| Transfers | p-queue (concurrency control) |
| Storage | electron-store (JSON) + Electron safeStorage (encryption) |
| Fonts | Geist Sans, Geist Mono |
| Icons | lucide-react |
- Node.js >= 18
- npm >= 9
git clone https://github.com/qwrobins/aether.git
cd aether
npm installnpm startLaunches the app in development mode with Vite HMR for the renderer process.
npm run packageBuilds the app into a platform-specific executable (not an installer).
npm run makeCreates distributable installers. Configured makers:
| Platform | Format |
|---|---|
| Windows | Squirrel |
| macOS | DMG |
| Linux | DEB, RPM, AppImage |
Download the latest macOS DMG from the GitHub Releases page, open it, and drag Aether into /Applications.
Release DMGs are named with an explicit platform and architecture, such as Aether-0.1.16-macos-arm64.dmg or Aether-0.1.16-macos-x64.dmg.
Published macOS release builds are signed with a Developer ID Application certificate and notarized by Apple, so they should open normally under Gatekeeper without clearing quarantine attributes.
Merging a releasable conventional commit to main runs .github/workflows/prepare-release.yml. That workflow calculates the next semantic version, updates package.json, package-lock.json, .release-please-manifest.json, and CHANGELOG.md, pushes the matching vX.Y.Z tag, then dispatches .github/workflows/release.yml for that tag. The release workflow validates that the tag matches package.json before building Linux and macOS release assets.
Tagged macOS releases are signed and notarized by GitHub Actions. The workflow imports a Developer ID Application certificate, signs the app bundle, submits it for Apple notarization, staples the notarization ticket, and publishes the DMG with the other release assets.
| Secret | Description |
|---|---|
MACOS_CERTIFICATE_BASE64 |
Base64-encoded .p12 export of the Developer ID Application certificate |
MACOS_CERTIFICATE_PASSWORD |
Password used when exporting the .p12 certificate |
APPLE_ID |
Apple ID email for notarization |
APPLE_APP_SPECIFIC_PASSWORD |
App-specific password for the Apple ID |
APPLE_TEAM_ID |
Apple Developer Team ID |
MACOS_SIGN_IDENTITY |
Optional exact Developer ID Application: ... identity name |
To create MACOS_CERTIFICATE_BASE64 after exporting the certificate from Keychain Access:
base64 -i DeveloperIDApplication.p12 | pbcopyLocal macOS builds remain unsigned unless AETHER_SIGN_MACOS=true and the notarization environment variables are present.
Release DMGs should not show this warning because they are signed and notarized. Unsigned local builds or ad hoc test artifacts may still be blocked by macOS with a "damaged" error. Run this once in Terminal after installing an unsigned build:
xattr -cr /Applications/Aether.appAfter building, install the latest AppImage into your applications menu:
npm run install:appimageYou can also pass a specific file:
./scripts/install-appimage.sh /path/to/Aether-0.1.2-x64.AppImagesrc/
├── main/ # Main process (Node.js)
│ ├── index.ts # App lifecycle, BrowserWindow
│ ├── ipc/ # IPC handler registration
│ │ ├── connection.handlers.ts # Connection CRUD
│ │ ├── filesystem.handlers.ts # Local filesystem ops
│ │ ├── s3.handlers.ts # S3 connect/disconnect + object ops
│ │ ├── sftp.handlers.ts # SFTP connect/disconnect + file ops
│ │ ├── network-filesystem.handlers.ts # SMB/NFS/WebDAV and object storage browsing
│ │ ├── rsync.handlers.ts # Rsync-backed browsing and transfers
│ │ ├── taildrop.handlers.ts # Taildrop target/receive IPC
│ │ └── transfer.handlers.ts # Transfer queue management
│ ├── services/ # Business logic
│ │ ├── connection.service.ts # Profile storage + encryption
│ │ ├── credential.service.ts # Electron safeStorage wrapper
│ │ ├── filesystem.service.ts # Local fs operations
│ │ ├── s3.service.ts # AWS S3 client + operations
│ │ ├── sftp.service.ts # SSH2 SFTP client + operations
│ │ ├── network-filesystem.service.ts # Mounted share, Azure Blob, and GCS operations
│ │ ├── rsync.service.ts # Rsync command integration
│ │ ├── taildrop.service.ts # Local Tailscale CLI operations
│ │ └── transfer.service.ts # Transfer engine (p-queue)
│ └── utils/
│ └── store.ts # JSON file persistence
├── preload/
│ └── preload.ts # contextBridge (invoke, on, removeAllListeners)
├── renderer/ # Renderer process (React)
│ ├── App.tsx # Root component
│ ├── index.css # Tailwind v4 theme + animations
│ ├── components/
│ │ ├── connection/ # Connection manager sheet + forms
│ │ ├── layout/ # AppLayout, AppSidebar, TitleBar
│ │ ├── panels/ # LocalPanel, RemotePanel, FileList, FileItem
│ │ ├── shared/ # EmptyState, FileIcon, FileSize
│ │ ├── transfer/ # TransferQueue, TransferItem
│ │ └── ui/ # 21 shadcn/ui primitives
│ ├── hooks/ # useKeyboardShortcuts, useTransferEvents, useFileSystem
│ └── stores/ # Zustand stores (connection, local, remote, taildrop, transfer, ui)
└── shared/ # Cross-process (types only)
├── constants/
│ └── channels.ts # IPC channel name constants
└── types/
├── connection.ts # S3, SFTP, network filesystem, and object storage profile types
├── filesystem.ts # FileEntry, DirectoryListing
├── ipc.ts # Fully typed IPC map
├── taildrop.ts # Taildrop target/status types
└── transfer.ts # Transfer item/request/progress types
Aether follows strict Electron process separation:
- Main process handles all Node.js, filesystem, network, and AWS SDK operations. Services are never imported by the renderer.
- Preload script exposes a minimal
window.apibridge viacontextBridgewithinvoke(request/response) andon(event subscription). - Renderer process is a pure React app. All backend operations go through
window.api.invoke(), fully typed viaIpcInvokeMap. - Shared directory contains only TypeScript types and constants — no runtime code with side effects.
Security: contextIsolation: true, nodeIntegration: false, sandbox: true. Credentials are encrypted via safeStorage before being written to disk.
All channels are defined in src/shared/constants/channels.ts and typed in src/shared/types/ipc.ts.
| Category | Channels |
|---|---|
| Filesystem | fs:read-dir, fs:stat, fs:mkdir, fs:delete, fs:rename, fs:get-home |
| Connections | conn:list, conn:save, conn:delete, conn:test, conn:connect, conn:disconnect |
| S3 | s3:list-buckets, s3:list-objects, s3:delete-object, s3:create-folder, s3:list-profiles, s3:list-roles |
| SFTP | sftp:list, sftp:mkdir, sftp:delete, sftp:rename |
| Network filesystems | netfs:list, netfs:mkdir, netfs:delete, netfs:rename |
| Rsync | rsync:list, rsync:mkdir, rsync:delete, rsync:rename |
| Taildrop | taildrop:status, taildrop:list-targets, taildrop:receive |
| Transfers | transfer:start, transfer:cancel, transfer:clear, transfer:list |
| Events | transfer:progress, transfer:complete, transfer:error |
| Window | window:close, window:minimize, window:maximize |