diff --git a/.github/workflows/selfhosted-build.yml b/.github/workflows/selfhosted-build.yml new file mode 100644 index 00000000..0d935ec7 --- /dev/null +++ b/.github/workflows/selfhosted-build.yml @@ -0,0 +1,138 @@ +name: Build Self-Hosted + +on: + workflow_dispatch: + inputs: + server_url: + description: 'Your server URL (e.g., http://myserver:3001)' + required: true + type: string + build_mac_arm64: + description: 'Build for macOS (Apple Silicon)' + default: true + type: boolean + build_mac_x64: + description: 'Build for macOS (Intel)' + default: false + type: boolean + +permissions: + contents: read + +jobs: + build-mac-arm64: + if: ${{ inputs.build_mac_arm64 }} + runs-on: macos-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Bun + uses: oven-sh/setup-bun@v1 + + - name: Install dependencies + run: bun install + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + targets: 'aarch64-apple-darwin' + + - name: Build native Rust modules (arm64) + run: | + for module in global-key-listener audio-recorder text-writer active-application selected-text-reader; do + echo "Building $module for arm64..." + cd native/$module + cargo build --release --target aarch64-apple-darwin + cd ../.. + done + + - name: Set up environment for self-hosted + run: | + echo "VITE_GRPC_BASE_URL=\"${{ inputs.server_url }}\"" >> .env + echo "VITE_ITO_ENV=\"dev\"" >> .env + echo "ITO_ENV=\"dev\"" >> .env + echo "CSC_IDENTITY_AUTO_DISCOVERY=false" >> .env + cat .env + + - name: Build Electron app + run: bun run electron-vite build + + - name: Package macOS DMG (arm64) + env: + CSC_IDENTITY_AUTO_DISCOVERY: false + run: | + bunx electron-builder --config electron-builder.config.js --mac dmg --arm64 --publish=never + + - name: Rename artifact with server info + run: | + # Extract hostname from URL for naming + SERVER_HOST=$(echo "${{ inputs.server_url }}" | sed 's|.*://||' | sed 's|:.*||' | sed 's|/.*||') + mv dist/Ito-Installer.dmg "dist/Ito-selfhosted-${SERVER_HOST}-arm64.dmg" || true + ls -la dist/*.dmg + + - name: Upload macOS DMG (arm64) + uses: actions/upload-artifact@v4 + with: + name: Ito-macOS-arm64 + path: dist/*.dmg + + build-mac-x64: + if: ${{ inputs.build_mac_x64 }} + runs-on: macos-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Bun + uses: oven-sh/setup-bun@v1 + + - name: Install dependencies + run: bun install + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + targets: 'x86_64-apple-darwin' + + - name: Build native Rust modules (x64) + run: | + for module in global-key-listener audio-recorder text-writer active-application selected-text-reader; do + echo "Building $module for x64..." + cd native/$module + cargo build --release --target x86_64-apple-darwin + cd ../.. + done + + - name: Set up environment for self-hosted + run: | + echo "VITE_GRPC_BASE_URL=\"${{ inputs.server_url }}\"" >> .env + echo "VITE_ITO_ENV=\"dev\"" >> .env + echo "ITO_ENV=\"dev\"" >> .env + echo "CSC_IDENTITY_AUTO_DISCOVERY=false" >> .env + cat .env + + - name: Build Electron app + run: bun run electron-vite build + + - name: Package macOS DMG (x64) + env: + CSC_IDENTITY_AUTO_DISCOVERY: false + run: | + bunx electron-builder --config electron-builder.config.js --mac dmg --x64 --publish=never + + - name: Rename artifact with server info + run: | + SERVER_HOST=$(echo "${{ inputs.server_url }}" | sed 's|.*://||' | sed 's|:.*||' | sed 's|/.*||') + mv dist/Ito-Installer.dmg "dist/Ito-selfhosted-${SERVER_HOST}-x64.dmg" || true + ls -la dist/*.dmg + + - name: Upload macOS DMG (x64) + uses: actions/upload-artifact@v4 + with: + name: Ito-macOS-x64 + path: dist/*.dmg diff --git a/README.md b/README.md index a3b1a8f1..5333613c 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,105 @@ bun run lint:fix # Fix linting issues --- +## 🏠 Self-Hosting + +This fork includes support for self-hosting Ito with a remote server. Instead of using the managed cloud service, you can run your own server and connect to it from anywhere. + +### Server Setup + +1. **Clone and configure the server**: + ```bash + git clone https://github.com/evoleinik/ito.git + cd ito/server + cp .env.example .env + ``` + +2. **Edit `.env`** with your settings: + ```bash + PORT=3001 # Use 3001 if 3000 is in use + DB_HOST=db + DB_USER=ito + DB_PASS=your-secure-password + GROQ_API_KEY=your-groq-api-key # Get from console.groq.com + S3_ENDPOINT=http://minio:9000 + REQUIRE_AUTH=false # For personal use + ``` + +3. **Start the server** (requires Docker): + ```bash + docker compose up -d --build + ``` + +4. **Run database migrations**: + ```bash + docker exec -it ito-server node ./node_modules/node-pg-migrate/bin/node-pg-migrate.js up + ``` + +5. **Verify the server is running**: + ```bash + curl http://localhost:3001 + # Should return: "Welcome to the Ito Connect RPC server!" + ``` + +### Building the Client (GitHub Actions) + +The easiest way to build your own client is using GitHub Actions: + +1. **Fork this repository** to your GitHub account +2. Go to **Actions** → **Build Self-Hosted** +3. Click **Run workflow** +4. Enter your server URL (e.g., `http://myserver:3001`) +5. Select your platform (macOS arm64 or x64) +6. Wait for the build to complete (~5-10 minutes) +7. Download the DMG from **Artifacts** + +### Building the Client (Local) + +Alternatively, build locally with your server URL: + +```bash +# Build with remote server URL (replace with your server address) +VITE_GRPC_BASE_URL=http://your-server:3001 \ +VITE_ITO_ENV=dev \ +CSC_IDENTITY_AUTO_DISCOVERY=false \ +bun run electron-vite build + +# Create DMG (macOS, arm64 only to avoid electron-builder issues) +bunx electron-builder --config electron-builder.config.js --mac dmg --arm64 --publish=never +``` + +### Installing the Client + +1. Mount the DMG from `dist/Ito-Installer.dmg` +2. Drag `Ito.app` to Applications +3. **Re-sign the app** (required for ad-hoc signed builds): + ```bash + xattr -cr /Applications/Ito.app + codesign --force --deep --sign - /Applications/Ito.app + ``` +4. Launch the app + +### Network Setup + +For remote access, you can use: +- **Tailscale**: Recommended for secure, zero-config networking. Install on both client and server machines, then use the Tailscale hostname (e.g., `http://box:3001`) +- **VPN**: Any VPN that gives you direct network access to your server +- **Port forwarding**: Expose port 3001 (not recommended for security reasons) + +### Server Auto-Start + +The Docker services are configured with `restart: always`, so they'll automatically start on boot. Ensure Docker is enabled: + +```bash +# Linux +sudo systemctl enable docker + +# Verify services are running +docker compose ps +``` + +--- + ## 🏗️ Architecture ### Client Architecture diff --git a/lib/window/ipcEvents.ts b/lib/window/ipcEvents.ts index 502b1ab1..2c125785 100644 --- a/lib/window/ipcEvents.ts +++ b/lib/window/ipcEvents.ts @@ -605,12 +605,12 @@ export function registerIPC() { // Server health check handleIPC('check-server-health', async () => { try { - const response = await fetch( - `http://localhost:${import.meta.env.VITE_LOCAL_SERVER_PORT}`, - { - method: 'GET', - }, - ) + const serverUrl = + import.meta.env.VITE_GRPC_BASE_URL || + `http://localhost:${import.meta.env.VITE_LOCAL_SERVER_PORT || '3000'}` + const response = await fetch(serverUrl, { + method: 'GET', + }) if (response.ok) { const text = await response.text() @@ -634,7 +634,7 @@ export function registerIPC() { ? 'Connection timed out' : error.message?.includes('ECONNREFUSED') || error.message?.includes('fetch') - ? 'Local server not running' + ? 'Server not reachable' : error.message || 'Unknown error occurred' return {