No description
  • Dart 59.6%
  • Rust 26.9%
  • Nix 3.9%
  • CMake 3%
  • Kotlin 2.7%
  • Other 3.9%
Find a file
2026-07-02 15:07:45 +01:00
.forgejo/workflows fix: use more native forgejo action 2026-07-02 15:07:45 +01:00
app fix: background location heartbeat 10 min 2026-07-02 13:35:13 +01:00
backend feat: api capabilities 2026-06-14 23:02:18 +02:00
.envrc initial commit 2026-05-18 15:59:58 +02:00
.gitignore ci: manual build+release pipeline with signed APK 2026-07-02 12:50:46 +01:00
.woodpecker.yml ci: manual build+release pipeline with signed APK 2026-07-02 12:50:46 +01:00
CLAUDE.md initial commit 2026-05-18 15:59:58 +02:00
flake.lock initial commit 2026-05-18 15:59:58 +02:00
flake.nix initial commit 2026-05-18 15:59:58 +02:00
README.md initial commit 2026-05-18 15:59:58 +02:00
rust-toolchain.toml initial commit 2026-05-18 15:59:58 +02:00

Famtrack

A libre, self-hostable family location tracker — modern alternative to OwnTracks.

  • Backend: Rust + Axum + sqlx + PostgreSQL. At-rest encryption for locations, geofences, dead-zones, and battery samples (ChaCha20-Poly1305 with a server-held master key).
  • App: Flutter (Android-first; flake also supports Linux desktop). OpenStreetMap via flutter_map. UnifiedPush for push notifications — no Google services.
  • Notifications: server-side geofence enter/exit + battery-threshold evaluation. Push payloads carry meaningful summaries ("Alice arrived at Home").

Repo layout

backend/   Rust server + Postgres migrations + NixOS module + sqlx offline data
app/       Flutter app (Android + Linux)
flake.nix  Dev shells + package + NixOS module

Prerequisites

  • Nix with flakes enabled. Everything below assumes you're inside one of the dev shells.
  • For the Android app: ~6 GB free for Gradle + the Android SDK.

Backend — development

nix develop                           # default shell: Rust, Postgres, sqlx-cli, Flutter

1. Start a local Postgres

The unix socket dir must live outside the repo (the flake source filter rejects socket files):

initdb -D .pg --no-locale --encoding=UTF8 -U "$USER"
pg_ctl -D .pg -l .pg.log -o "-p 55432 -h 127.0.0.1 -k /tmp/famtrack-pgsock" start
createdb -h 127.0.0.1 -p 55432 famtrack

2. Write the dev .env

cp backend/.env.example backend/.env
# generate the at-rest encryption key (32 bytes hex)
sed -i "s/^DATA_KEY_HEX=.*/DATA_KEY_HEX=$(openssl rand -hex 32)/" backend/.env
sed -i "s/^DATABASE_URL=.*|DATABASE_URL=postgres://$USER@127.0.0.1:55432/famtrack|" backend/.env

3. Build & run

cd backend
export $(grep -v ^# .env | xargs)
cargo run                             # migrations apply on first boot

backend/scripts/smoke.sh exercises the full API (register → invite → join → encrypted geofence → location post → events → dead-zone). With the server running:

BASE=http://127.0.0.1:8080 ./backend/scripts/smoke.sh

4. After schema changes

sqlx::query! is compile-time checked. After editing a migration:

sqlx migrate run --database-url "$DATABASE_URL"
cargo sqlx prepare                    # refresh backend/.sqlx/ — commit it

The Nix package build relies on .sqlx/ (offline mode), so do not skip the second step.

App — development

nix develop .#android                 # extra shell with Android SDK, NDK, JDK 21
cd app

# First-time only: generate android/, linux/ platform scaffolding.
flutter create . --project-name=famtrack --org=xyz.mrfluffy --platforms=android,linux

flutter pub get
flutter analyze
flutter run                           # picks an attached device / emulator
flutter build apk --debug             # → build/app/outputs/flutter-apk/app-debug.apk

The Android emulator reaches a host-side server on http://10.0.2.2:8080. For a physical phone, set the server URL in the app's Settings screen.

App ↔ server reachability

Setup Server BIND_ADDR App baseUrl
Android emulator on dev host 127.0.0.1:8080 http://10.0.2.2:8080 (default)
Phone on LAN 0.0.0.0:8080 http://<host-lan-ip>:8080 (set in Settings)
Production 127.0.0.1:8080 behind nginx/caddy https://famtrack.example.com (set in Settings)

Server — deployment on NixOS (flake module)

The flake exports a NixOS module. In your host flake:

{
  inputs.famtrack.url = "github:yourorg/famtrack";  # or path:/srv/famtrack

  outputs = { self, nixpkgs, famtrack, ... }: {
    nixosConfigurations.my-host = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        famtrack.nixosModules.default
        ({ ... }: {
          services.famtrack-server = {
            enable = true;
            bindAddr = "127.0.0.1:8080";
            environmentFile = "/var/lib/secrets/famtrack.env";
            # provisionDatabase = true;  # default — also enables postgresql.service
            # openFirewall = false;      # default — front with nginx/caddy
          };
        })
      ];
    };
  };
}

Provision the secrets file

environmentFile must hold:

JWT_SECRET=<openssl rand -hex 32>
DATA_KEY_HEX=<openssl rand -hex 32>

Permissions:

install -d -m 0700 -o root -g root /var/lib/secrets
install -m 0600 -o root -g root /dev/stdin /var/lib/secrets/famtrack.env <<EOF
JWT_SECRET=$(openssl rand -hex 32)
DATA_KEY_HEX=$(openssl rand -hex 32)
EOF

For production: use sops-nix or agenix to keep these out of the Nix store and your git history.

⚠️ The data key is load-bearing. Losing DATA_KEY_HEX permanently bricks every encrypted row in the DB. Back it up offline.

Deploy

sudo nixos-rebuild switch --flake .#my-host
sudo systemctl status famtrack-server
sudo journalctl -u famtrack-server -f

The module:

  • creates a famtrack system user + group,
  • provisions a local Postgres + famtrack role/database (toggleable),
  • runs famtrack-server with strict systemd sandboxing,
  • waits on postgresql.service before starting.

Reverse proxy (nginx example)

services.nginx = {
  enable = true;
  recommendedProxySettings = true;
  recommendedTlsSettings = true;
  virtualHosts."famtrack.example.com" = {
    enableACME = true;
    forceSSL = true;
    locations."/" = {
      proxyPass = "http://127.0.0.1:8080";
      proxyWebsockets = true;          # required — clients hold WebSockets at /v1/ws/...
    };
  };
};

security.acme.acceptTerms = true;
security.acme.defaults.email = "you@example.com";

Just the package (no module)

nix build github:yourorg/famtrack#famtrack-server
./result/bin/famtrack-server          # needs DATABASE_URL, JWT_SECRET, DATA_KEY_HEX

UnifiedPush

The app uses UnifiedPush. To get push notifications working end-to-end:

  1. Install a distributor app on the phone (e.g. ntfy, NextPush).
  2. Open Famtrack — it picks up the distributor automatically, requests an endpoint, and registers it against the server (POST /v1/devices).
  3. The server posts a small JSON ping ({"type":"famtrack.event","summary":"…"}) to that endpoint when an event fires.

Without a distributor, the app still works while open — events arrive over the WebSocket.

Troubleshooting

  • cargo run says DATA_KEY_HEX not setexport $(grep -v ^# backend/.env | xargs) before running, or systemctl restart famtrack-server if you just changed the env file.
  • nix develop fails with unsupported type on .s.PGSQL.* — Postgres' unix socket landed in the repo. Always run pg_ctl with -k /tmp/famtrack-pgsock (or any path outside the flake), then rm -f .s.PGSQL.*.
  • flutter build apk "Duplicate class … tink" — already fixed in app/android/app/build.gradle.kts; if it returns, look for another transitive depending on com.google.crypto.tink:tink (the JVM build) and add the exclusion.
  • flutter build apk "Language version 1.7 is no longer supported" — an Android plugin pinned an old Kotlin language version. Bump that plugin (we already did this for unifiedpush 5 → 6).
  • Server-side build fails with sqlx "DATABASE_URL not set" — for cargo build set DATABASE_URL. For nix build we use SQLX_OFFLINE=true; you must have run cargo sqlx prepare since the last migration change and committed backend/.sqlx/.

License

AGPL-3.0-only.