Privee is a Phoenix LiveView umbrella application:
apps/priveeholds the domain logic and Ecto schemas.apps/privee_webholds the web layer: LiveViews, components and assets.
Chats are end-to-end encrypted with the Signal Protocol. See End-to-end encryption for the design, its trade-offs (local plaintext history, node-local ciphertext, one device per session) and the manual release checklist, and the E2EE audit for the independent review of the web, server and Android implementations.
Open Settings > Language before or after signing in to choose English, Italian, European Portuguese, Spanish or French. English is the default, regardless of browser language. The preference is stored in a browser-local cookie for one year, independently of your session, and survives login/logout. It is not synced to other browsers or the Android app.
Switching language updates the current page without navigating or restarting
encryption. Form drafts, messages and local chat history are preserved; existing
validation errors are cleared and subsequent validation uses the new language.
Messages, session names, recovery phrases and safety numbers are never translated.
Server and browser-owned UI share the Gettext catalogs in
apps/privee_web/priv/gettext.
Anyone can run their own Privee server on Fly.io: fork
PriveeDeploy, add your Fly.io keys,
and run its Deploy workflow. It builds Privee (or your fork of it) with your own
fly.toml. The step-by-step walkthrough is in the
PriveeDeploy README;
Self-hosting lists the server configuration and notes on
other platforms.
Clients work with any instance through its DNS name: the Privee Android app asks for a server address, and other clients can use the Client API.
- Architecture: umbrella apps, contexts, web layer, data model and where to change what.
- Technologies: the full stack with versions.
- Cross-repo contract: what the server and the Android app share (API, versioning, push, libsignal) and how changes and releases are coordinated.
- Client API, End-to-end encryption, E2EE audit, Self-hosting, IDE setup.
- AI agents: CLAUDE.md (Claude Code), .github/copilot-instructions.md (GitHub Copilot), AGENTS.md, and the skills in .claude/skills (mirrored in .github/skills).
Versions are pinned in .tool-versions for Erlang/OTP, Elixir and Node.js. CI reads the same file, so use asdf or mise to install matching versions:
mise install # or: asdf installYou also need a PostgreSQL server. The dev and test configs expect postgres/postgres on localhost:5432.
docker run --name privee-database -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres \
--restart=unless-stopped -p 5432:5432 -d postgres:18Podman accepts the same arguments (podman run ...).
mix setup # deps, database, esbuild/tailwind binaries, npm packages
mix phx.server # or: iex -S mix phx.serverThe app listens on localhost:4000.
Before pushing, run:
mix precommitIt compiles with --warnings-as-errors, unlocks unused deps, formats the code, then runs credo --strict, the tests and npm run check for the assets (prettier, tsc, eslint, vitest). CI runs the same checks in check-only mode, plus:
mix dialyzer(PLTs are stored inpriv/plts)- browser end-to-end tests (below)
- a Docker image build
- a check that
.claude/skillsand.github/skillsare identical
Playwright specs in apps/privee_web/assets/e2e drive real browsers through registration and encrypted chats: messages both ways, bursts, an offline recipient, reloads, and a check that no plaintext crosses the WebSocket. Each simulated user gets its own browser context, so it is a separate Signal device.
cd apps/privee_web/assets
npm run e2e:install # once: downloads Chromium
npm run e2e # starts `mix phx.server` and runs the specsTo reuse a running server, set E2E_BASE_URL (e.g. E2E_BASE_URL=http://localhost:4000 npm run e2e). To use an installed browser instead of the downloaded Chromium, set E2E_BROWSER_CHANNEL (e.g. msedge or chrome). CI runs the specs against a production build (MIX_ENV=prod, mix assets.deploy) and uploads the report and traces when they fail.
Enable the pre-commit hook, which runs mix precommit:
git config core.hooksPath .githooksEditor setup notes are in docs/ide-setup.md.
ci.yml runs on pull requests, on every push to main and on demand: Elixir checks and tests (with Postgres), Dialyzer, asset checks, Playwright browser tests, a Docker build and the skills sync check.
This repository never deploys. Deployments run on demand from PriveeDeploy, which checks out a chosen commit of Privee and deploys it to Fly.io; the upstream instance and its fly.toml live there. Merging to main only runs CI. To deploy from a workstation, choose a full source SHA with successful CI:
gh workflow run deploy.yml -R MaxDac/PriveeDeploy -f ref=<full-source-sha>AI agents follow the deploy-privee skill, which checks CI and asks for confirmation first.
The release reads its configuration from environment variables, listed in Self-hosting. rel/env.sh.eex detects Fly through FLY_APP_NAME and sets the node name and IPv6 distribution; outside Fly the node falls back to a short name.
Chat storage is node-local. Encrypted messages are kept in ETS on the node serving the conversation and are lost on restart, so the chat must run as a single machine. See End-to-end encryption.
Privee is free software, licensed under the
GNU Affero General Public License v3.0 only (AGPL-3.0-only).
Every fork, modified version or derived work must stay under the same licence,
and anyone who runs a Privee server for other people must offer them the
source code of the version they run. You cannot use this code in proprietary or
non-FOSS software. See NOTICE for details and third-party components.