A.P.E: AWS Platform Explorer

A single 16MB Go binary that serves a Finder-style browser UI for EC2 file management and S3 browsing. Connect via SSH, drag-and-drop upload files, edit with Monaco, and browse S3 buckets — no SCP commands, no tab-switching.

Role

Solo Engineer (full stack)

Tech Stack
Go 1.26React 18TypeScriptTailwind CSSVitegolang.org/x/crypto/sshgithub.com/pkg/sftpaws-sdk-go-v2Monaco Editor

The Challenge

Managing EC2 files traditionally means juggling scp, ssh, and vim across terminal tabs. Existing GUI tools (Cyberduck, Transmit) require separate installs, licensing, or lack S3 integration. The challenge was shipping a zero-install GUI — one binary the user drops in $PATH — that handles SSH auth, live SFTP operations, S3 browsing, and serves a full React frontend, all without Node.js or a separate web server.

Architecture & Deep Dive

System Architecture

Single 16MB Go Binary: Embedded React + SSH/SFTP + S3 + Monaco

Browser(localhost:9000)Go HTTP Server(embed.FS)REST API RouterSSH Auth ManagerSFTP ClientEC2 File SystemMonaco Editor (UI)AWS SDK v2S3 Buckets

0. Binary Bootstrap — main.go

markdown
$ ape [--host ec2-user@54.x.x.x] [--key ~/.ssh/id_rsa]
  │
  ├─ Parse CLI flags (host, key, port, bind-addr)
  ├─ sshClient.Dial("tcp", host+":22") → ssh.NewSession()
  │    └─ sftp.NewClient(sshConn) → SFTP session
  ├─ s3Client = aws.NewConfig() → s3.NewFromConfig()
  │
  ├─ Embed React build via //go:embed dist/*
  │    └─ http.FileServer(http.FS(embedFS)) → /
  │
  ├─ Register API routes:
  │    ├─ GET  /api/files?path=   → sftp.ReadDir()
  │    ├─ GET  /api/file?path=    → sftp.Open() → stream
  │    ├─ POST /api/upload        → multipart → sftp.Create()
  │    ├─ POST /api/mkdir         → sftp.MkdirAll()
  │    ├─ DEL  /api/delete        → sftp.Remove()
  │    ├─ GET  /api/s3/buckets    → s3.ListBuckets()
  │    └─ GET  /api/s3/objects    → s3.ListObjectsV2()
  │
  └─ http.ListenAndServe(bindAddr, mux)
       └─ Web UI ready at http://localhost:9000

1. Upload Pipeline — multipart → SFTP stream

markdown
POST /api/upload  [multipart/form-data: file, path]
  │
  ├─ r.ParseMultipartForm(32 MB)
  ├─ file, header = r.FormFile("file")
  ├─ remotePath = r.FormValue("path") + "/" + header.Filename
  │
  ├─ sftpClient.Create(remotePath)   ← opens remote file for writing
  │    └─ If parent dir missing → sftpClient.MkdirAll(dir)
  │
  ├─ io.Copy(remoteFile, file)       ← streams bytes, no local temp
  │    └─ Progress tracked via custom io.Writer wrapper
  │         └─ SSE stream → client EventSource (drag & drop bar)
  │
  └─ res.JSON({ success: true, path: remotePath, size: header.Size })

2. React File Explorer — Finder-style UX

markdown
FileExplorer (React 18 + TypeScript)
  │
  ├─ State: { path, entries, view: "grid"|"list", selected: Set<string> }
  │
  ├─ Grid View: thumbnail cards with file-type icons (Monaco-derived)
  ├─ List View: sortable table (name / size / modified)
  │
  ├─ Right-click context menu:
  │    ├─ Open in Monaco Editor  → GET /api/file → editor modal
  │    ├─ Download               → anchor[download] blob URL
  │    ├─ Rename                 → POST /api/rename
  │    └─ Delete                 → DEL /api/delete (confirm dialog)
  │
  ├─ Drag & Drop zone:
  │    └─ onDrop → FormData → POST /api/upload → SSE progress bar
  │
  ├─ Keyboard shortcuts:
  │    ├─ Cmd+N   → new folder dialog
  │    ├─ Delete  → delete selected
  │    └─ Cmd+C   → copy path to clipboard
  │
  └─ Breadcrumb nav: click any segment → navigate to that path

Technical Trade-offs

  • Single Binary (embed.FS): React build is embedded at compile time via //go:embed dist/*. No separate web server, no Node.js runtime — the user runs one binary and gets the full UI.
  • Direct SFTP Streaming: io.Copy(remoteFile, file) streams upload bytes directly to the EC2 filesystem without writing a local temp file, keeping memory footprint flat regardless of file size.
  • SSE Upload Progress: Server-Sent Events push byte-count updates to the browser without polling. Avoids WebSocket complexity for a one-directional data flow.
  • AWS SDK v2 (no CLI dependency): S3 ops use the Go SDK directly rather than shelling out to aws s3 ls. Credentials read from ~/.aws/credentials or env vars — same auth model users already have.
  • Monaco Editor in-browser: Code editing happens client-side. The Go server only serves the file bytes; Monaco handles syntax highlighting, search, and editing locally, keeping the server stateless.

Reliability & Validation

Test Coverage

CI via GitHub Actions on every push; Go build matrix (linux/amd64, darwin/arm64, darwin/amd64, windows/amd64).

Validation

Manual integration tests against a live EC2 t2.micro and real S3 buckets. Edge cases exercised across OS-specific SSH key formats (PEM, OpenSSH new format).

Edge cases validated
  • SSH auth — missing key file (clear error), passphrase-protected key (prompt), permission denied (403 JSON)
  • SFTP — upload to read-only path (permission error surfaced), mkdir race condition (MkdirAll idempotent)
  • S3 — no credentials configured (SDK error surfaced as 401), empty bucket (empty list, no crash)
  • Upload — file > 32 MB (multipart limit, 413), non-UTF-8 filename (percent-encoded safely)
  • Monaco — binary file opened for edit (hex fallback mode), very large file (lazy load chunks)

Error Handling Strategy

  • All API handlers return { error: string } JSON on failure — the React UI surfaces these as toast notifications.
  • SFTP session reconnects automatically on disconnect (exponential backoff, max 3 retries).
  • S3 errors are unwrapped with errors.As(err, &apiErr) to surface the AWS error code to the user.

Impact & Collaboration

  • Eliminated the scp/ssh/vim workflow for EC2 file management — drag, drop, edit, done.
  • Single 16MB binary with zero runtime dependencies; distributable via brew install or direct GitHub Release download.
  • CI release pipeline (GitHub Actions) cross-compiles for 4 targets (linux/amd64, darwin/arm64, darwin/amd64, windows/amd64) on every tagged release.
  • Monaco editor brings VS Code-level syntax highlighting and search to remote files without a plugin.