Thank you for contributing to KillNode. This guide covers setting up the development environment, the coding standards, and the release process.
- Prerequisites
- Repository Setup
- Running the Website Locally
- Running the Desktop App Locally
- Building and Packaging
- Code Style & Standards
- Testing
- Making a Pull Request
- Cutting a Desktop Release
- Deploying the Website
- Environment Variables Reference
| Tool | Version | Why |
|---|---|---|
| Node.js | 20 LTS or 22 LTS | Runtime for website and desktop tooling |
| npm | 10+ | Package manager (workspaces) |
| Git | Any modern | Version control |
| Python | 3.11 | Required by node-gyp for native modules (distutils was removed in 3.12+) |
| Tor binary | any stable | Desktop dev/testing — see docs/INSTALL.md |
| PostgreSQL | 15+ or Neon account | Website local dev |
Optional:
- Docker — simplest way to run a local PostgreSQL instance.
- VS Code — the repo includes
extensions.jsonwith recommended extensions.
git clone https://github.com/Alaustrup/killnode.git
cd killnode
# Install all workspace dependencies from the single lock file
npm installThis installs dependencies for both website/ and desktop/ workspaces. Shared packages are hoisted to the root node_modules/.
cd website
cp .env.example .envEdit website/.env:
# Neon (recommended) or local Postgres
DATABASE_URL="postgresql://killnode:killnode@localhost:5432/killnode"
DIRECT_URL="postgresql://killnode:killnode@localhost:5432/killnode"
# Admin credentials (local dev only — change in production)
ADMIN_USERNAME="admin"
ADMIN_PASSWORD="killnode2026"
# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
ADMIN_SESSION_SECRET="<your-32-byte-hex-secret>"
# GitHub Releases widget
GITHUB_REPO_OWNER="Alaustrup"
GITHUB_REPO_NAME="killnode"
GITHUB_TOKEN="" # optional; increases rate limitdocker run -d \
--name kn-pg \
-e POSTGRES_USER=killnode \
-e POSTGRES_PASSWORD=killnode \
-e POSTGRES_DB=killnode \
-p 5432:5432 \
postgres:16# Inside website/
npx prisma generate
npx prisma migrate deploy
npx prisma db seed # loads sample posts from data/posts.jsoncd .. # back to monorepo root
npm run dev:web
# → http://localhost:3000The admin panel is at http://localhost:3000/admin/login.
# From the monorepo root — generates the Prisma client then opens electron-vite dev
npm run dev:desktopThis runs:
prisma generate --schema prisma/schema.prisma(insidedesktop/)electron-vite dev— starts the renderer dev server, watches main/preload, and launches Electron.
Environment variable: DATABASE_URL is set automatically to file:./data/desktop.db in the desktop .env.example. Create desktop/.env if you need to override:
DATABASE_URL="file:./data/desktop.db"The SQLite database is created automatically on first launch inside Electron's userData directory. The data/ path in the env var is only used by prisma generate during build/dev — the runtime path is derived from app.getPath("userData").
The download script fetches, verifies (SHA256), and extracts the Tor Expert Bundle automatically:
# Run once from desktop/ — places tor.exe / tor in resources/tor/
node scripts/download-tor.mjsThe script skips the download if the binary already exists (fast-path). Set FORCE_TOR_DOWNLOAD=1 to re-download. The binary is gitignored — each developer runs the script once.
Alternatively, install system Tor (sudo apt-get install tor) or Tor Browser — KillNode's path search will find either.
npm run build:web # prisma generate + next build
npm run start:web # serve the production build locallynpm run build:desktop # prisma generate + electron-vite build
# output: desktop/out/npm run package:desktop # downloads + verifies Tor, builds, packages with electron-builder
# output: desktop/release/The package script runs three steps in order:
node scripts/download-tor.mjs— fetches and SHA256-verifies the Tor Expert Bundlenpm run build— Prisma generate + electron-vite buildelectron-builder --publish never— creates platform installers
Artifacts:
desktop/release/KillNode-*-win.exe— Windows NSIS installerdesktop/release/KillNode-*-win.zip— Windows portabledesktop/release/KillNode-*-linux.AppImage— Linux portabledesktop/release/KillNode-*-linux.deb— Debian/Ubuntu package
npm run typecheck:desktop # prisma generate + tsc --noEmit
npm run lint:web # ESLint on the websitestrict: trueis enabled in alltsconfig.jsonfiles. Fix all type errors before submitting a PR.- Prefer explicit types for function parameters and return values in public APIs.
- Use
unknowninstead ofanywhere possible. Ifanyis necessary, add an// eslint-disable-next-line @typescript-eslint/no-explicit-anycomment with a brief justification.
- Comment why, not what. Do not comment obvious syntax.
- Add comments for non-obvious design decisions, security trade-offs, and workarounds (e.g., the ESM dynamic-import pattern in
torrent-service.ts).
electron-vite bundles the main process as CommonJS. Do not use static import for packages that are ESM-only (webtorrent, magnet-uri). Always use dynamic await import() via a lazy-loading getter, as in torrent-service.ts. See docs/ARCHITECTURE.md for the full explanation.
Use the Conventional Commits format:
<type>(<scope>): <short description>
[optional body]
Types: feat, fix, docs, chore, refactor, test, ci.
Scopes: desktop, website, ci, docs.
Examples:
feat(desktop): add circuit-count display to status bar
fix(desktop): replace static ESM import with dynamic import() for webtorrent
docs: rewrite USAGE.md as end-user help guide
ci: pin Python 3.11 for node-gyp compatibility
The project does not yet have a formal automated test suite (Phase 1 hardening). Current validation:
| Check | Command | What it covers |
|---|---|---|
| TypeScript typecheck | npm run typecheck:desktop |
All main-process types |
| ESLint | npm run lint:web |
Website code style and quality |
| Next.js build | npm run build:web |
Full website build (catches runtime import issues) |
| Desktop build | npm run build:desktop |
Full Electron build |
Before submitting a PR, run all four checks and ensure they pass. CI will verify the same.
For changes that touch Tor, proxy, or killswitch logic, manually verify:
- Tor activates and the status bar goes green.
-
curl --proxy http://127.0.0.1:9742 https://check.torproject.org/api/ipreturns a Tor exit IP. - Deactivating Tor clears the Electron session proxy.
- Adding a magnet link creates a torrent row with telemetry.
- The killswitch fires without errors on your target platform.
- Fork the repository and create a feature branch:
git checkout -b feat/my-feature
- Make your changes, following the code style above.
- Run all validation checks locally (typecheck, lint, build).
- Push your branch and open a PR against
main. - The CI workflow will automatically run on your PR. All checks must pass.
- Address review comments and update your branch.
PR description should include:
- What changed and why.
- How to test the change manually.
- Any security implications.
Only the maintainer pushes release tags. Process:
- Update the version in
desktop/package.json(and optionallywebsite/package.jsonif the website is also updated). - Commit the version bump:
git add desktop/package.json git commit -m "chore: bump desktop version to 0.2.0" git push origin main - Tag and push:
git tag v0.2.0 git push origin v0.2.0
- The
release-desktop.ymlworkflow runs automatically and:- Packages Windows and Linux installers.
- Uploads them to a new GitHub Release for that tag.
- The website's download widget picks up the new release automatically (via
/api/releases/latest).
- Import the repo at vercel.com/new.
- The
vercel.jsonat the monorepo root configures everything automatically — do not set a custom root directory in Vercel's UI. - Add the required environment variables in the Vercel dashboard (see Section 11).
- Deploy.
Every push to main triggers an automatic Vercel deployment. No manual action needed.
Vercel's build command does not run prisma migrate deploy (removed to avoid Neon cold-start timeouts). Apply migrations manually before pushing:
# Local machine with direct Neon connection string
cd website
DIRECT_URL="<neon-direct-url>" npx prisma migrate deployThen push your code — the next Vercel deployment will run prisma generate and pick up the updated schema.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL pooled connection (Neon pgBouncer) |
DIRECT_URL |
Yes | PostgreSQL direct connection (for Prisma Migrate) |
ADMIN_USERNAME |
No | Admin login username (default: admin) |
ADMIN_PASSWORD |
No | Admin login password (default: killnode2026) |
ADMIN_SESSION_SECRET |
Yes (prod) | 32+ byte hex secret for JWT signing |
GITHUB_REPO_OWNER |
Yes | GitHub user/org owning the killnode repo |
GITHUB_REPO_NAME |
Yes | Repository name (killnode) |
GITHUB_TOKEN |
No | GitHub PAT for higher API rate limits |
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes (build) | SQLite path used by prisma generate (e.g., file:./data/desktop.db) — runtime path is set by the app from app.getPath("userData") |
| Secret | Used by | Description |
|---|---|---|
GITHUB_TOKEN |
release-desktop.yml |
Auto-provided by Actions — uploads release assets |
CSC_LINK |
release-desktop.yml |
Base64-encoded code-signing certificate (Phase 2) |
CSC_KEY_PASSWORD |
release-desktop.yml |
Password for the signing certificate (Phase 2) |