-
Notifications
You must be signed in to change notification settings - Fork 106
Add self-hosting support with remote server and GitHub Actions build #461
base: dev
Are you sure you want to change the base?
Conversation
The health check now uses VITE_GRPC_BASE_URL when set, allowing the app to connect to remote servers (e.g., via Tailscale) instead of requiring localhost. Falls back to VITE_LOCAL_SERVER_PORT for local development. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Complete guide for running a self-hosted Ito setup including: - Server setup with Docker and database migrations - Building client with custom server URL - Installing and re-signing the app - Network setup (Tailscale recommended) - Server auto-start configuration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Users can now build their own self-hosted Ito client via GitHub Actions: - Fork the repo - Run "Build Self-Hosted" workflow - Enter their server URL - Download the DMG artifact Supports macOS arm64 and x64 builds. No secrets required. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
WalkthroughThis pull request adds self-hosting infrastructure and documentation to the project. A new GitHub Actions workflow ( Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (9)
lib/window/ipcEvents.ts (1)
608-613: Consider adding a timeout for remote server health checks.Remote servers may have higher latency than local servers. The fetch call currently has no timeout, which could cause the health check to hang indefinitely if the server is unreachable or slow to respond.
Apply this diff to add a timeout:
- const response = await fetch(serverUrl, { - method: 'GET', - }) + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout + + const response = await fetch(serverUrl, { + method: 'GET', + signal: controller.signal, + }) + + clearTimeout(timeoutId)README.md (3)
273-274: Clarify security implications of REQUIRE_AUTH=false.While setting
REQUIRE_AUTH=falseis appropriate for personal self-hosting, it's important to emphasize that this should only be used on private networks. Consider adding a security note.Apply this diff to enhance the security guidance:
- REQUIRE_AUTH=false # For personal use + REQUIRE_AUTH=false # For personal use on private networks only
309-317: Local build command may have shell escaping issues.The multi-line environment variable syntax in the local build example may not work correctly in all shells. Consider providing a more portable format.
Apply this diff to provide a single-line format:
-# 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 +# 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
333-335: Port forwarding security warning could be stronger.The documentation mentions that port forwarding is "not recommended for security reasons," but this could be more explicit about the risks.
Apply this diff to strengthen the security warning:
-- **Port forwarding**: Expose port 3001 (not recommended for security reasons) +- **Port forwarding**: Expose port 3001 (**strongly discouraged** - exposes your server to the public internet without authentication).github/workflows/selfhosted-build.yml (5)
5-17: Consider adding URL validation for server_url input.The workflow accepts any string as
server_urlwithout validation. Invalid URLs could cause build failures later in the process. Consider adding a validation step or providing clearer input description with URL format examples.Update the input description to be more prescriptive:
server_url: - description: 'Your server URL (e.g., http://myserver:3001)' + description: 'Your server URL including protocol and port (e.g., http://192.168.1.100:3001 or http://myserver.tailscale.net:3001)' required: true type: string
43-50: Make Rust module builds more robust.The
cdcommands assume a specific directory structure and use relative paths. If the build fails in one module, the script continues with potentially incorrect working directory for subsequent modules.Apply this diff to make the builds more robust:
- 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 ../.. + (cd "native/$module" && cargo build --release --target aarch64-apple-darwin) || exit 1 doneThis uses a subshell to ensure each build starts from the correct directory and exits immediately if any build fails.
69-74: Silent failure in rename step could cause confusion.The
|| trueappended to themvcommand silently tolerates rename failures. If the rename fails, users will download a DMG with the default name instead of the expected name with server info, causing confusion.Apply this diff to provide better error handling:
- 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 + if [ -f "dist/Ito-Installer.dmg" ]; then + mv dist/Ito-Installer.dmg "dist/Ito-selfhosted-${SERVER_HOST}-arm64.dmg" + else + echo "Warning: Ito-Installer.dmg not found" + ls -la dist/ + fi - ls -la dist/*.dmg
76-80: Artifact upload path pattern could match unexpected files.The upload step uses
dist/*.dmgas the path pattern, which could match multiple DMG files if previous builds weren't cleaned up. This might upload unintended files.Apply this diff to make the upload more specific:
- name: Upload macOS DMG (arm64) uses: actions/upload-artifact@v4 with: name: Ito-macOS-arm64 - path: dist/*.dmg + path: dist/Ito-selfhosted-*-arm64.dmg
23-137: Consider using a matrix strategy to reduce duplication.The two jobs (
build-mac-arm64andbuild-mac-x64) contain nearly identical steps with only minor differences in target architecture and artifact naming. A matrix strategy could reduce this duplication and make the workflow easier to maintain.This is a structural suggestion for future improvement—the current approach is functional and clear, but a matrix-based approach would reduce code duplication by ~50%.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.github/workflows/selfhosted-build.yml(1 hunks)README.md(1 hunks)lib/window/ipcEvents.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Always prefer console commands over log commands. Use
console.loginstead oflog.info
Files:
lib/window/ipcEvents.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/always.mdc)
Never use empty catch statements
Files:
lib/window/ipcEvents.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/code-conventions.mdc)
**/*.{ts,tsx}: Follow standard, idiomatic TypeScript coding practices for structure, naming, and types
Avoid adding comments unless they explain complex logic or non-obvious decisions; well-written, self-explanatory code is preferred
Do not add comments that merely restate what the code does
Rely on comprehensive tests to document the behavior and usage of code rather than extensive comments within the code itself
Use kebab-case when naming directories, TypeScript, and other files
**/*.{ts,tsx}: Prefer interfaces over types for object definitions
Use type for unions, intersections, and mapped types
NEVER useanyoras anytypes or coercion
Leverage TypeScript's built-in utility types
Use generics for reusable type patterns
Use PascalCase for type names and interfaces
Use camelCase for variables and functions
Use UPPER_CASE for constants
Use descriptive names with auxiliary verbs (e.g., isLoading, hasError)
Prefix interfaces for React props with 'Props' (e.g., ButtonProps)
Keep type definitions close to where they're used
Export types and interfaces from dedicated type files when shared
Co-locate component props with their components
Use explicit return types for public functions
Use arrow functions for callbacks and methods
Implement proper error handling with custom error types
Use function overloads for complex type scenarios
Prefer async/await over Promises
Prefer function declarations over function expressions
Use readonly for immutable properties
Leverage discriminated unions for type safety
Use type guards for runtime type checking
Implement proper null checking
Avoid type assertions unless necessary
Handle Promise rejections properly
Files:
lib/window/ipcEvents.ts
lib/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Shared TypeScript modules, preload logic, and unit tests should be organized in
lib/
Files:
lib/window/ipcEvents.ts
{app,lib}/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Prettier with 2-space indent for code formatting across TypeScript and React files
Files:
lib/window/ipcEvents.ts
{app,lib,server}/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
{app,lib,server}/**/*.{ts,tsx,js,jsx}: Use ESLint to enforce code style and runbun run lintbefore submitting code
Always useconsolecommands instead of log commands (e.g.,console.loginstead oflog.info)
Files:
lib/window/ipcEvents.ts
{app,lib}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
{app,lib}/**/*.{ts,tsx}: Components and classes usePascalCasenaming convention
Hooks and utility functions usecamelCasenaming convention
Files:
lib/window/ipcEvents.ts
{app,lib,server}/**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
Constants use
SCREAMING_SNAKE_CASEnaming convention
Files:
lib/window/ipcEvents.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: run-tests / test
- GitHub Check: native-build-check / build-check-windows
- GitHub Check: native-build-check / build-check-mac
🔇 Additional comments (1)
lib/window/ipcEvents.ts (1)
632-643: LGTM! Error message updated for self-hosting.The error message change from "Local server not running" to "Server not reachable" is appropriate for the self-hosting use case where the server may be remote.
| - 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, explore repository structure to understand build setup
find . -maxdepth 2 -type f \( -name "vite.config.*" -o -name "electron-builder.config.*" -o -name "package.json" -o -name ".env*" \) | head -20Repository: heyito/ito
Length of output: 156
🏁 Script executed:
# Search for .env parsing and VITE_GRPC_BASE_URL usage
rg -n "VITE_GRPC_BASE_URL" -A 2 -B 2Repository: heyito/ito
Length of output: 2683
🏁 Script executed:
# Look for vite config to understand how env vars are loaded
fd -e "vite.config" -e "vite.config.js" -e "vite.config.ts"Repository: heyito/ito
Length of output: 77
🏁 Script executed:
# Check for .env example files to see intended format
fd ".env" --type fRepository: heyito/ito
Length of output: 67
🏁 Script executed:
# Search for how environment variables are parsed in the project
rg -n "process\.env\.|import\.meta\.env\." --type ts --type js | head -30Repository: heyito/ito
Length of output: 2531
🏁 Script executed:
cat .env.exampleRepository: heyito/ito
Length of output: 442
🏁 Script executed:
cat electron.vite.config.tsRepository: heyito/ito
Length of output: 1807
🏁 Script executed:
cat electron-builder.config.jsRepository: heyito/ito
Length of output: 3294
🏁 Script executed:
# Check if there are any comments or docs about .env format in the repo
rg -n "\.env" --type md | head -20Repository: heyito/ito
Length of output: 1049
🏁 Script executed:
cat -n .github/workflows/selfhosted-build.yml | sed -n '50,120p'Repository: heyito/ito
Length of output: 2818
🏁 Script executed:
# Search for how CSC_IDENTITY_AUTO_DISCOVERY is used in electron-builder
rg -n "CSC_IDENTITY_AUTO_DISCOVERY" -A 2 -B 2Repository: heyito/ito
Length of output: 1010
Remove CSC_IDENTITY_AUTO_DISCOVERY from .env file—it's already set as an environment variable.
The CSC_IDENTITY_AUTO_DISCOVERY=false setting is written to .env (line 57) but is also set directly as a shell environment variable in the electron-builder step (line 65). Since electron-builder reads this from the shell environment, the .env entry is redundant. Set it only via the env: directive in the build step to avoid duplication.
The quoted environment variables (e.g., VITE_GRPC_BASE_URL="...") are correctly formatted—Vite's dotenv parser automatically strips surrounding quotes.
🤖 Prompt for AI Agents
In .github/workflows/selfhosted-build.yml around lines 52 to 58, the workflow
currently appends CSC_IDENTITY_AUTO_DISCOVERY=false into the .env file even
though that variable is already provided via the shell environment for the
electron-builder step; remove the line that echoes CSC_IDENTITY_AUTO_DISCOVERY
into .env so the variable is only set via the build step's env: directive and
avoid duplication, and verify no other workflow steps write the same variable
into .env.
| docker exec -it ito-server node ./node_modules/node-pg-migrate/bin/node-pg-migrate.js up | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Check if the migration script exists at the documented path
fd -t f "node-pg-migrate.js" server/Repository: heyito/ito
Length of output: 36
🏁 Script executed:
# Also check the overall directory structure and package.json
cat -n package.json | head -50Repository: heyito/ito
Length of output: 2673
🏁 Script executed:
# Look for node-pg-migrate in node_modules or check if it's installed
fd -t d "node-pg-migrate" . --max-depth 3Repository: heyito/ito
Length of output: 36
🏁 Script executed:
# Check the README around the context to understand the migration setup better
sed -n '270,300p' README.mdRepository: heyito/ito
Length of output: 958
🏁 Script executed:
# Search for node-pg-migrate in the entire repository
rg "node-pg-migrate" .Repository: heyito/ito
Length of output: 5064
🏁 Script executed:
# Check if there's a server package.json
fd -t f "package.json" server/Repository: heyito/ito
Length of output: 99
🏁 Script executed:
# Check Docker-related files for dependency installation
fd -t f "Dockerfile" . --max-depth 2Repository: heyito/ito
Length of output: 73
🏁 Script executed:
# Check docker-compose configuration
fd -t f "docker-compose.yml" . --max-depth 2Repository: heyito/ito
Length of output: 81
🏁 Script executed:
# Check Dockerfile for dependency installation
cat server/DockerfileRepository: heyito/ito
Length of output: 569
🏁 Script executed:
# Check the server-level npm scripts for migration commands
sed -n '20,50p' server/package.jsonRepository: heyito/ito
Length of output: 1577
Add missing migrations directory argument to the Docker exec command.
The Docker migration command should include the --migrations-dir flag to specify the correct migrations directory. The actual migration script in server/scripts/migrate.sh uses --migrations-dir src/migrations, but the README command omits this argument. Update the command to:
docker exec -it ito-server node ./node_modules/node-pg-migrate/bin/node-pg-migrate.js --migrations-dir src/migrations up🤖 Prompt for AI Agents
In README.md around lines 283 to 284, the docker exec command for running
migrations omits the --migrations-dir argument; update the command to include
the migrations directory used by the project's migration script (use
--migrations-dir src/migrations) so the Docker command mirrors
server/scripts/migrate.sh and points node-pg-migrate at the correct migrations
folder.
|
Heartbreaking that you are having to shut down. This is a hugely important tool. Thank you for your work on it, and for open sourcing it going forward with self-hosting options. |
Summary
With the managed cloud service shutting down on December 31, 2025, this PR adds complete self-hosting support so users can run their own Ito servers and build custom clients.
Changes
Remote server support (
lib/window/ipcEvents.ts)VITE_GRPC_BASE_URLwhen set, allowing connection to remote servers (e.g., via Tailscale)VITE_LOCAL_SERVER_PORTfor local developmentGitHub Actions workflow (
.github/workflows/selfhosted-build.yml)http://myserver:3001)Documentation (
README.md)User Experience
Testing
🤖 Generated with Claude Code