Files
BalanceServer/README.md
T
2026-10-29 18:43:28 +07:00

6.3 KiB

Balance Server

Self-hosted synchronization, account and web server for the Balance iOS, macOS, watchOS and Android applications. It is a single Rust process with a bundled SQLite engine and a complete browser client, and runs on Linux, Windows and macOS.

Quick start with Docker

cp .env.example .env
openssl rand -base64 48
# Paste the generated value into BALANCE_JWT_SECRET in .env
docker compose up -d --build
curl http://localhost:8123/health

Open http://localhost:8123 to register or sign in to the web application. Data is kept in the balance-data Docker volume. Back it up while the container is stopped.

Run without Docker

Install Rust 1.90 or newer, then build the locked dependencies:

cargo build --locked --release

The binary is target/release/balance-server (balance-server.exe on Windows). SQLite is compiled into the binary; no separate SQLite service is needed.

On Linux or macOS:

export BALANCE_JWT_SECRET="replace-with-a-long-random-secret"
export BALANCE_DB_PATH="./data/balance.db"
./target/release/balance-server

On Windows PowerShell:

$env:BALANCE_JWT_SECRET = "replace-with-a-long-random-secret"
$env:BALANCE_DB_PATH = ".\data\balance.db"
.\target\release\balance-server.exe

For another platform, build on that platform or install its Rust target and C toolchain for bundled SQLite. cargo build --locked --release --target <target> requires the matching linker and C compiler. The retained Go implementation is a compatibility reference and rollback option: go build -o balance-server-go ./cmd/balance-server.

After starting the binary, open http://localhost:8080 in a browser. The web assets are embedded into the executable, so there is no separate frontend build, Node.js process or static directory to deploy.

Web application

The responsive web client includes:

  • registration, login, automatic session refresh and logout;
  • dashboard, current balance and manual balance adjustment;
  • adding, editing, deleting, searching and filtering transactions;
  • analytics for 1, 3 and 12 months, spending breakdown and savings rate;
  • monthly category budgets;
  • custom categories with 50 icons, custom emoji and 24 colors;
  • light, dark and system themes, plus RUB, EUR, USD and SEK formatting;
  • automatic synchronization every 30 seconds and whenever the browser tab becomes active;
  • installable PWA shell for desktop and mobile browsers.

The browser session uses HttpOnly, SameSite=Strict cookies. Native applications continue to use Bearer access tokens and are fully backward compatible. Only display preferences are saved in browser local storage; tokens and financial records are not stored there.

Configuration

Variable Default Description
BALANCE_ADDR :8080 Listen address
BALANCE_DB_PATH ./data/balance.db SQLite file path
BALANCE_JWT_SECRET required Random secret of at least 32 characters
BALANCE_ALLOW_REGISTRATION true Allows new accounts
BALANCE_ACCESS_TTL 15m Access-token lifetime
BALANCE_REFRESH_TTL 720h Refresh-token lifetime
BALANCE_CORS_ORIGINS empty Comma-separated browser origins; native apps do not need CORS

HTTPS

Use HTTPS before exposing the server to a network. The Apple applications accept plain HTTP only for localhost and 127.0.0.1; a physical device should connect through an HTTPS reverse proxy such as Caddy, nginx or a private VPN with TLS. Example Caddy configuration:

balance.example.com {
    reverse_proxy 127.0.0.1:8123
}

Then enter https://balance.example.com in each application's Settings → Server account.

API

Method Endpoint Purpose
GET /health Health check
POST /v1/auth/register Create account
POST /v1/auth/login Sign in
POST /v1/auth/refresh Rotate session tokens
POST /v1/auth/logout Revoke refresh token
GET /v1/me Current account
POST /v1/sync Push and pull changes

The sync endpoint accepts transactions, custom categories and monthly budgets. The server isolates records by account, stores an append-only change cursor, and resolves competing edits with last-write-wins timestamps. Current Apple and Android clients use push-only requests followed by paginated pulls; update the server and applications together.

Operational notes

  • Registration can be disabled after the required accounts have been created.
  • Passwords are stored as salted Argon2id hashes. Refresh tokens are stored only as SHA-256 hashes.
  • Back up the SQLite database and its -wal/-shm files together, or stop the server before copying the database.
  • Changing BALANCE_JWT_SECRET invalidates access tokens. Existing refresh sessions remain valid and will receive new access tokens after refresh.
  • The in-process authentication limiter allows 30 authentication requests per IP per minute. Use a reverse proxy for stronger public rate limiting and request logging.

Compatibility and migration

The Rust server retains the HTTP/JSON API, embedded web client, environment variables, SQLite schema, Argon2id password hashes, JWT format, refresh sessions and sync cursor semantics of the Go implementation. Docker Compose still maps host port 8123 to container port 8080 and keeps /data in balance-data. Direct binary execution defaults to port 8080.

Before replacing a running Go server, stop it, back up its database directory (including any SQLite WAL/SHM files), and preserve BALANCE_JWT_SECRET. Start Rust against the same BALANCE_DB_PATH; do not run both writers simultaneously. Existing accounts, access/refresh tokens and saved device cursors remain usable. See deployment and rollback for the full procedure.

Verify the implementations locally with Go 1.25+, Rust and Python 3:

cargo test --locked
cargo fmt --check
cargo clippy --locked --all-targets -- -D warnings
cargo build --locked
python3 tools/compatibility.py
go test ./...
go vet ./...
go build ./...

The Python standard-library suite builds the Go reference, starts both binaries on temporary loopback ports and databases, compares API responses and assets, and tests SQLite takeover and rollback in both directions. To use release or prebuilt executables, pass --rust-binary PATH and optionally --go-binary PATH.