Development setup
In progress
The repository holds the plans, the docs, the website, the Rust crypto core and sync server, and the web vault. This page grows as the apps arrive.
Requirements
Section titled “Requirements”| Tool | Version |
|---|---|
| Node.js | 22.13 or newer (24 recommended; the e2e suite uses node:sqlite) |
| pnpm | 11 |
| Rust | The version in rust-toolchain.toml, installed by rustup, with the wasm32-unknown-unknown target |
| wasm-pack | cargo install wasm-pack --locked |
| Google Chrome | For the end-to-end tests only |
| Git | Any recent version |
Run the website
Section titled “Run the website”git clone https://github.com/octap-app/octapass.gitcd octapasspnpm installpnpm dev # http://localhost:4321Other root scripts:
pnpm build # static build into apps/website/distpnpm preview # serve the build locallypnpm check # type-check the websiteRun the web vault
Section titled “Run the web vault”The web vault runs on the real stack: the Rust crypto core compiled to WebAssembly, in a Web Worker, talking to octapass-server. One command starts both:
pnpm installpnpm --filter @octapass/web dev:stack # server on :8080, web vault on http://localhost:5174It builds the server (cargo build -p octapass-server) and, whenever its Rust sources changed since the last build, the WASM core (pnpm --filter @octapass/core build:if-stale). pnpm build:web, pnpm start and the browser end-to-end tests check the core the same way, so a pull that changes the Rust crates never leaves them on an old build. The server runs in dev mode with a throwaway data directory that’s deleted when you press Ctrl-C; set OCTAPASS_DATA_DIR=./somewhere to keep it. In dev mode the sign-up screen shows the email verification code, so no mail server is needed.
To run the two halves yourself:
OCTAPASS_DEV_MODE=true OCTAPASS_WEB_ORIGINS=http://localhost:5174 cargo run -p octapass-serverpnpm --filter @octapass/web dev # talks to http://localhost:8080VITE_SERVER_URL points the web vault at another server. VITE_VAULT_BACKEND=mock (or pnpm --filter @octapass/web dev:mock) runs it on the in-browser mock instead, with example items and no server, which is handy for UI work and demos.
cargo test --workspace # crypto core, test vectors, serverpnpm --filter @octapass/core test # the WASM flows in Node (after its build)pnpm --filter @octapass/vault-client test # item model, tools, mock, HTTP client, worker bridgepnpm --filter @octapass/vault-client test:server # the real service against a real octapass-serverpnpm --filter @octapass/web test # UI tests, on the mockpnpm --filter @octapass/web e2e # Chrome against a real server: see belowpnpm --filter @octapass/web build # type-check and production buildEach Rust crate’s integration tests build as one binary, tests/main.rs, which declares every file in tests/ as a module. That way cargo links the crate once rather than once per file. To run one file’s tests, use cargo test -p octapass-server --test integration sharing::. A new test file needs a mod line in tests/main.rs; a test there fails if you forget it. The test-vector files in octapass-core stay as their own binaries, so --test vectors regenerates only its own files.
The e2e suite (apps/web/e2e) builds the server and the web vault, starts both on free ports with a fresh data directory, and drives the system Chrome with playwright-core. It signs up, checks that the server’s database files hold no plaintext from the vault, locks and unlocks, signs in again with the Secret Key, unlocks with a virtual passkey (WebAuthn PRF), syncs an edit from a second browser, and deletes the account, checking that no table still mentions it. Set HEADED=1 to watch, and CHROME_PATH if Chrome isn’t in /Applications.
Serve it, or run the native apps
Section titled “Serve it, or run the native apps”pnpm start builds the web vault and the server and serves both on one port, 127.0.0.1:8380, for a tunnel or a proxy to publish. Dev mode stays off. The first run in a terminal asks two things:
- Where people open it: through a tunnel or proxy at an address you choose (for example
https://pass.example.com), or only on this computer athttp://localhost:8380. - Who can create an account: only the emails you invite, anyone who can open the address, or no one new. Your git email is the suggested invite, and you can change it or add more.
It offers to save the answers in the gitignored .env.local at the repository root, and pnpm start --setup asks again. Without a terminal, or with OCTAPASS_NO_PROMPT=1, it uses the defaults and prints them. pnpm serve does the same without building.
The native apps’ scripts work out the device and the server, and ask only when they can’t tell:
| Command | Device | Server |
|---|---|---|
pnpm dev:ios |
The booted Simulator, else a choice | pnpm dev:api or pnpm start on this Mac, whichever is running |
pnpm dev:ios:device |
The connected iPhone or iPad, else a choice | pnpm start’s public address, or one you type, since a phone can’t reach the Mac’s localhost |
pnpm dev:android |
The only emulator or phone, else a choice | pnpm dev:api or pnpm start on this computer, through adb reverse |
pnpm dev:ios:device also asks which team signs the app. The choices come from the accounts signed in to Xcode and the Apple Development certificates in the keychain, and the answers can be saved in the gitignored apps/ios/device.env. Each command takes --setup to ask every question again. Environment variables skip the questions: OCTAPASS_SERVER_URL, DEVICE, APPLE_DEVELOPMENT_TEAM and ANDROID_SERIAL.
The iPhone and Android commands rebuild the app’s Rust library whenever its sources changed since the last build, because the committed Swift and Kotlin bindings change with it. When an Xcode build fails, they print its error lines and the path of the full log in apps/ios/build/.
Build the apps
Section titled “Build the apps”Each app has a build command at the repository root:
| Command | What it builds | Where it runs |
|---|---|---|
pnpm build:extension |
Chrome and Firefox, unpacked in apps/extension/dist |
Anywhere |
pnpm build:safari |
The Safari apps for macOS and the iOS Simulator | A Mac |
pnpm build:ios |
The iPhone and iPad app, signed for App Store Connect (--upload uploads it) |
A Mac |
pnpm build:android |
The Android app, as an unsigned release APK | Anywhere with the Android SDK |
pnpm build:desktop |
The desktop app for the system you’re on: build:macos, build:windows or build:linux |
Anywhere |
pnpm build:macos |
The Mac app and disk image, in apps/macos/build |
A Mac |
pnpm build:windows |
The Windows installer | Windows, or cross-compiled on a Mac |
pnpm build:linux |
.deb, .rpm and AppImage packages |
Linux |
pnpm build:cli |
The octapass command and its Git and Docker helpers |
Anywhere |
pnpm build:vscode |
The VS Code extension, as a .vsix |
Anywhere |
pnpm build:jetbrains |
The JetBrains plugin, as a .zip |
Anywhere with JDK 17 |
The extensions and the iPhone, Android and desktop apps offer one server as “Octa Pass” on their sign-in screen, and anyone can still type another under “Self-hosted”. The first build in a terminal asks which server that is. It offers pnpm start’s public address and the hosted service, and can save the answer in .env.local as OCTAPASS_BUILD_SERVER_URL. Without a terminal the builds use the hosted service. VITE_SERVER_URL, which the release workflow sets, wins over both, and --setup asks again. The apps accept only https:// addresses, or http://localhost on the same computer.
The builds are unsigned unless the app’s own signing settings are set, as each app’s README describes. The Windows and Linux builds need the Tauri CLI (cargo install tauri-cli --locked --version 2.12.4).
pnpm build:macos builds the Mac app (apps/macos), the native SwiftUI app that is Octa Pass on a Mac. The Tauri app (apps/desktop) is for Windows and Linux only and no longer builds for macOS. The Mac app needs XcodeGen and an Apple developer team, from APPLE_DEVELOPMENT_TEAM or the one pnpm dev:ios:device signs the iPhone app with: the app keeps its keys in the Mac’s Keychain, which needs a signed app, so there’s no unsigned build. It’s signed for development, so it opens on the Macs registered with that team. Developer ID signing and notarisation, for downloads anyone can open, aren’t set up yet.
Where things live
Section titled “Where things live”| Path | Contents |
|---|---|
plans/ |
Internal plans and decisions, numbered and read in order |
docs/ |
These docs, served at /docs/ |
changelog/ |
One Markdown file per notable change, served at /changelog/ |
skills/octapass-docs/ |
The agent skill for querying these docs |
apps/website/ |
Astro + Starlight website |
apps/web/ |
The web vault (Vite + React) |
packages/ui/ |
Design tokens and React UI primitives |
packages/vault-client/ |
Item model, VaultService interface, local tools, the real service (src/remote) and the mock |
packages/core/ |
The WASM build of the crypto core (@octapass/core) |
crates/ |
Rust: octapass-core (crypto, items, tools), octapass-wasm (bindings), octapass-api (API types) |
apps/server/ |
The sync server (axum + SQLite) |
AGENTS.md |
Rules for AI coding agents |
The planned layout for the apps and crates is in architecture.
Building the site for a real domain
Section titled “Building the site for a real domain”The site URL and repository URL come from apps/website/src/site.config.ts. Override them at build time:
PUBLIC_SITE_URL=https://octap.app PUBLIC_REPO_URL=https://github.com/octap-app/octapass pnpm build