Building from Source
Ghost is built from four separate codebases that are combined into a single desktop application. Think of it like building a car — the engine (Go backend), the dashboard (React frontend), the body (Tauri desktop shell), and the accessories (Chrome extension) are each built separately, then assembled into the final product. This page explains how to build each piece and how they fit together.
Prerequisites
Section titled “Prerequisites”| Tool | Version | Why it’s needed | Install |
|---|---|---|---|
| Go | 1.25+ | Compiles the backend — the proxy engine, REST API, WebSocket hub, AI agent, SQLite database, and all business logic | go.dev/dl |
| Node.js | 20+ | Runs the frontend build tools (Vite, TypeScript compiler, Tailwind CSS) | nodejs.org |
| npm | 10+ | Installs frontend and extension JavaScript dependencies | Included with Node.js |
| Rust | Latest stable | Compiles the Tauri desktop shell — the native window, system tray, menus, and OS integration layer | rustup.rs |
| Xcode CLI Tools | Latest | Required on macOS for simctl (iOS simulator control), codesign (app signing), and system headers | xcode-select --install |
Optional tools for security features:
| Tool | Purpose |
|---|---|
| Python 3 | Required for sqlmap and semgrep security scanners |
| Frida | Dynamic instrumentation for mobile apps (installed via Ghost’s UI) |
| ADB | Android device control (install via Android SDK) |
Quick Start
Section titled “Quick Start”Development Mode (Two Terminals)
Section titled “Development Mode (Two Terminals)”The fastest way to start developing is to run the Go backend and React frontend separately. This gives you hot module replacement (instant UI updates without rebuilding) for the frontend and race detection for the backend.
Terminal 1 — Go backend:
make devThis runs go run -race -tags dev ./cmd/ghost, which:
- Starts the proxy on port 4545 and the API on port 5565
- Enables Go’s race detector (catches concurrent access bugs)
- Uses
-tags devwhich replaces thego:embeddirective with an empty filesystem — the Go binary doesn’t try to embed or serve the frontend
Terminal 2 — React frontend:
cd frontend && npm run devThis starts Vite’s development server on port 5173 with:
- Hot Module Replacement (HMR) — edit a React component and see the change instantly without a page reload
- TypeScript type checking on save
- Proxy rules that forward API and WebSocket requests to the Go backend
Development Mode (Desktop App)
Section titled “Development Mode (Desktop App)”To run the full desktop app with the Tauri shell:
make desktop-devThis starts both the Vite dev server and the Tauri Rust shell simultaneously. The Rust shell launches the Go sidecar and navigates the WebView to the Vite dev server, so you get HMR inside the native desktop window.
Vite Dev Server Proxy
Section titled “Vite Dev Server Proxy”When developing without Tauri (the two-terminal approach), the Vite dev server proxies API requests to the Go backend so the frontend can communicate with it:
| Path | Target | Protocol |
|---|---|---|
/api/* | http://localhost:9090 | HTTP |
/ws/* | ws://localhost:9090 | WebSocket |
/health | http://localhost:9090 | HTTP |
Important: The proxy target is hardcoded to port 9090 in frontend/vite.config.ts. The Go backend defaults to port 5565. For the proxy to work, either:
- Run Go on port 9090: set
[api] port = 9090in~/.ghost/config.toml - Or change the proxy target in
vite.config.tsto5565
When running through Tauri (desktop mode), this proxy is not used — the Tauri shell navigates directly to the Go sidecar’s API port.
Build Targets
Section titled “Build Targets”Go Backend
Section titled “Go Backend”The Go backend compiles into a single binary that contains everything: the HTTP proxy, REST API, WebSocket hub, SQLite database, AI agent, addon engine, and (in production) the entire React frontend embedded as static files.
# Development — hot-reload friendly, no frontend embeddingmake dev # go run -race -tags dev ./cmd/ghost
# Production — standalone binary with embedded frontendmake build # Output: dist/ghost
# Sidecar binaries — for embedding inside the Tauri desktop appmake sidecar-darwin-arm64 # Output: frontend/src-tauri/binaries/ghost-engine-aarch64-apple-darwinmake sidecar-darwin-amd64 # Output: frontend/src-tauri/binaries/ghost-engine-x86_64-apple-darwinmake sidecar-windows # Output: frontend/src-tauri/binaries/ghost-engine-x86_64-pc-windows-msvc.exe
# Dev sidecars — same filenames but with -tags dev (no frontend embedding)make sidecar-dev-darwin-arm64make sidecar-dev-darwin-amd64make sidecar-dev-windowsProduction vs. dev builds: The key difference is the -tags dev flag. In production, the Go binary uses go:embed to bundle the entire frontend/dist/ directory inside itself — the binary becomes a self-contained web server that serves the React frontend from memory. In dev mode, this embedding is replaced with an empty filesystem, so the Go binary doesn’t need the frontend to be built at all. The frontend is served separately by Vite’s dev server.
Sidecar naming: The filenames follow Tauri’s naming convention (ghost-engine-{rust-target-triple}). Tauri looks for these specific filenames in frontend/src-tauri/binaries/ when building the desktop app.
React Frontend
Section titled “React Frontend”cd frontend
npm install # Install all dependencies from package.jsonnpm run dev # Start Vite dev server on port 5173 with HMRnpm run build # TypeScript type-check (tsc -b) then Vite production buildnpm run lint # ESLint across all source filesnpm run preview # Serve the production build locally for testingBuild output: frontend/dist/ — contains minified HTML, CSS, JavaScript, and font files. Source maps are generated as “hidden” (exist for Sentry stack traces but are not served to the browser).
Key dependencies:
- React 19 with TypeScript ~5.9
- Vite 7 for building and dev server
- Tailwind CSS v4 (via
@tailwindcss/viteplugin) - Zustand 5 for state management
- Monaco Editor for code editing (addons, Frida scripts)
@tanstack/react-virtualfor virtualized lists (flow list, element trees)react-resizable-panelsfor the split-pane layout
Tauri Desktop App
Section titled “Tauri Desktop App”cd frontend
npx tauri dev # Development mode (Vite dev server + Tauri shell)npx tauri build # Production build (DMG installer + updater bundle)The Tauri configuration lives in frontend/src-tauri/tauri.conf.json and controls window settings, bundle targets, security policies, and the updater configuration.
What tauri build produces:
- macOS: A
.dmginstaller and a.app.tar.gz+.sig(compressed app bundle with EdDSA signature for the auto-updater) - Windows: An NSIS installer that installs per-user (no admin required)
Chrome Extension
Section titled “Chrome Extension”cd extension
npm installnpm run build # Build service worker + content scriptnpm run dev # Watch mode for developmentnpm run typecheck # TypeScript check without buildingBuild process: The extension uses two separate Vite configurations:
- Main build (
vite.config.ts): Compiles the service worker and popup as ES modules - Content script build (
vite.content.config.ts): Compiles the content script as an IIFE (Immediately Invoked Function Expression) because Chrome content scripts cannot use ES moduleimportstatements
The content script build uses emptyOutDir: false to avoid erasing the main build output. Both builds target ES2022 and produce minified output in the extension/dist/ directory.
Loading in Chrome for development:
- Build the extension:
cd extension && npm run build - Open Chrome →
chrome://extensions - Enable “Developer mode”
- Click “Load unpacked” and select the
extension/dist/folder
Cross-Compilation
Section titled “Cross-Compilation”Go Sidecar
Section titled “Go Sidecar”Go makes cross-compilation straightforward. Since Ghost’s Go code is pure Go (no CGo dependencies), you can build for any supported platform from any machine:
# macOS Apple Silicon (from any OS)GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w" -o ghost-engine ./cmd/ghost
# macOS Intel (from any OS)GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o ghost-engine ./cmd/ghost
# Windows x64 (from any OS)GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o ghost-engine.exe ./cmd/ghostCGO_ENABLED=0 ensures no C compiler is needed and the resulting binary has zero external dependencies. The CI pipeline sets this explicitly, though the Makefile relies on Go’s default behavior (which also works since there are no CGo imports).
Universal macOS Binary
Section titled “Universal macOS Binary”make desktop-macos-universalThis builds both ARM64 and x64 sidecar binaries, then merges them with Apple’s lipo tool into a single “universal binary” that runs natively on both Apple Silicon and Intel Macs. The resulting binary is roughly double the size but eliminates the need for users to pick the right download.
Build Flags
Section titled “Build Flags”| Flag | Used in | Purpose |
|---|---|---|
-tags dev | make dev, dev sidecars | Replaces go:embed with an empty filesystem, so the Go binary doesn’t need the frontend built. Makes Go compilation faster during development. |
-race | make dev, make test | Enables Go’s race detector, which catches concurrent access bugs at runtime. Adds ~10x overhead, so only used in development and testing. |
-ldflags="-s -w" | All production builds | -s strips the symbol table, -w strips DWARF debug information. Together they reduce binary size by approximately 30%. |
CGO_ENABLED=0 | CI pipeline, cross-compilation | Forces pure Go compilation with no C dependencies. Required for cross-compilation (can’t use a macOS C compiler to build for Windows). |
Testing and Quality
Section titled “Testing and Quality”# Run all Go tests with race detection and coveragemake test # go test -race -cover -count=1 ./...
# Verbose test output (shows each test name)make test-v # go test -race -cover -count=1 -v ./...
# Skip long-running tests (integration tests)make test-short # go test -race -short -count=1 ./...
# Generate HTML coverage reportmake cover # Runs tests, then opens coverage.html
# Static analysismake vet # go vet ./...make lint # golangci-lint run ./...
# Code formattingmake fmt # gofmt -s -w + goimports -wThe -count=1 flag disables test caching, ensuring tests always run fresh. This is important because Ghost’s tests interact with SQLite databases and network ports that may have different state between runs.
Utility Commands
Section titled “Utility Commands”# Remove all build artifacts (dist/, frontend/dist/, coverage files, sidecar binaries)make clean
# Emergency cleanup — kill everything Ghost-relatedmake nukemake nuke is the nuclear option for when things are stuck. It:
- Kills all
ghost-engine,ghost-desktop, and Cargo/Tauri build processes - Kills any WebDriverAgent (
xcodebuild.*wda) builds - Disables the system proxy on macOS (via
networksetup) - Removes the SQLite database (
~/.ghost/ghost.db) - Removes all build artifacts
- Checks that ports 4545, 5565, and 1420 are free
Use this when Ghost didn’t shut down cleanly and leftover processes are holding ports or the system proxy is stuck pointing at a dead Ghost instance.
How the Pieces Fit Together
Section titled “How the Pieces Fit Together”When you run make desktop (or make desktop-macos-arm64), the build process is:
- Go sidecar is compiled and placed in
frontend/src-tauri/binaries/with the correct target-triple filename - Frontend is built by Vite into
frontend/dist/— the Go sidecar’s production embed picks this up - Tauri compiles the Rust shell, bundles the Go sidecar as an external binary, and packages everything into a DMG/NSIS installer
At runtime:
- The user launches Ghost (double-click the app)
- The Tauri Rust shell starts and spawns the Go sidecar with
--sidecarflag - The Go sidecar picks ephemeral ports, starts the proxy and API, and prints
{"api_port": N, "proxy_port": N, "token": "..."}to stdout - The Tauri shell reads this JSON and navigates the WebView to
http://localhost:{api_port} - The React frontend loads from the Go binary’s embedded filesystem and connects to the API and WebSocket