- Rust 70.1%
- JavaScript 13%
- Shell 9.2%
- CSS 6.1%
- Kotlin 1%
- Other 0.5%
|
yann degat
1848be0f40
Two fixes exposing daemon state to the user: 1. `src/jni_bridge.rs` — bridge `tracing` events to logcat. Default `tracing_subscriber::fmt()` writes to stdout, which is captured to /dev/null for non-system Android processes. That's why the daemon appeared silent after start (logcat showed only the JNI-side `log::info!` calls). New inline `AndroidLogWriter` routes each tracing event through `log::info!` → android_logger → logcat. Losing per-event level is acceptable for debugging; everything lands on the `fogss-trace` tag. 2. `MainActivity` — new "VIEW AGENT LOGS" button on the ERROR screen. Execs `logcat -d -v time -t 400 fogss:V fogss-tlspin:V fogss-trace:V AndroidRuntime:E *:S` and displays the result in a scrollable selectable TextView. COPY button for clipboard. Works on debug-signed APKs; release-signed APKs restrict own-log reads (will need an in-memory ring buffer). Unblocks debugging the current "daemon didn't answer within 15 s" failure — previously the daemon's tracing output was invisible. |
||
|---|---|---|
| .forgejo/workflows | fix(android-ci): use nix-patched aapt2 instead of AGP's Maven-downloaded one | |
| android | feat(android): bridge tracing→logcat + in-app log viewer on ERROR screen | |
| docs | feat(ci): publish linux binary + OCI image + APK on every main push | |
| scripts | Initial public release | |
| src | feat(android): bridge tracing→logcat + in-app log viewer on ERROR screen | |
| tests | Initial public release | |
| web | Initial public release | |
| .gitignore | Initial public release | |
| AGENTS.md | Initial public release | |
| build.rs | Initial public release | |
| Cargo.lock | Initial public release | |
| Cargo.toml | Initial public release | |
| CLAUDE.md | Initial public release | |
| devshell.nix | fix(devshell): pin perl for openssl-src build.rs in hermetic CI containers | |
| down.sh | Initial public release | |
| flake.lock | Initial public release | |
| flake.nix | Initial public release | |
| LICENSE | Initial public release | |
| Makefile | fix(android-ci): use nix-patched aapt2 instead of AGP's Maven-downloaded one | |
| README.md | Initial public release | |
| up.sh | Initial public release | |
FOGSS — Friend-Only Git Synchronized Storage
Distributed, git-backed object store with an S3-compatible API and libp2p P2P replication between mutually-trusted peers. One Rust binary. Friend-only trust. Works offline.
- S3 plane — stock
aws s3 cpworks. SigV4, per-store credentials. - Git plane — stock
git clone/push/pullworks over SSH. gitoxide, LFS for large blobs. - Replication — libp2p gossipsub + request-response, AEAD-encrypted per
tribe. Only peers with mutual
tribe peer addsync. - UI — web UI embedded in the binary on port 2204 (HTTPS).
- App frontends on top of the store — each store has a
kind; today a store can be a plain S3/Git bucket (generic) or a Vaultwarden-compatible password vault (bitwarden), served athttps://<host>:2204/bw/<tribe>/<store>for stock Bitwarden clients (bwCLI, desktop, mobile). The pattern — pick a store kind, mount a protocol adapter over the same git-backed bytes — is how future frontends (contacts, notes, calendar, ...) will plug in. - Encrypted at rest — every bare git repo and every LFS blob is
encrypted on disk with tribe-key-derived keys (Content-Preserving
Encryption, CPE). Git OIDs stay SHA-1 of the plaintext; on-disk bytes
are ciphertext. SQLCipher also protects
agent.dbandruntime.db. Nothing sensitive sits in plaintext on the filesystem.
Build
make release # native release build
make release-musl # static musl build (portable single binary)
The build runs npm install + npm run build in web/ and embeds
web/dist/ into the binary. Set SKIP_WEB_BUILD=1 to skip.
Nix dev shell: nix develop (see devshell.nix).
First run
# Create identity + first tribe. Prompts for passphrase; prints 24-word
# recovery phrase — save it.
fogss agent init
# Start daemon. Prompts for passphrase.
fogss agent start
Default data directory:
| OS | Path |
|---|---|
| Linux | ~/.local/share/fogss |
| macOS | ~/Library/Application Support/fogss |
| Windows | %APPDATA%\fogss |
Override with fogss -d <path> ....
Default bind addresses (override with fogss agent start --<surface>-bind ...):
| Surface | Default address |
|---|---|
| Web UI (HTTPS) | 127.0.0.1:2204 |
| S3 (HTTPS+SigV4) | 127.0.0.1:8080 |
| Git SSH | 127.0.0.1:2222 |
| libp2p P2P | :4001 |
| Prometheus | 0.0.0.0:9090 |
Install the TLS cert (browser / CLI / Node)
The HTTPS surfaces use a self-signed CA generated at agent init. Trust it in
user-scope stores without sudo:
# Downloads the CA+leaf bundle from a running daemon and installs it
# into ~/.pki/nssdb (Chromium), ~/.mozilla/firefox/*, and writes
# ~/.config/fogss/{server.crt,ca.crt} for env-var use.
scripts/fogss-trust-cert.sh https://localhost:2204
After install:
- Browsers —
https://localhost:2204works without warnings. - curl / git —
export CURL_CA_BUNDLE=~/.config/fogss/server.crt. - Node —
export NODE_EXTRA_CA_CERTS=~/.config/fogss/ca.crt. - aws CLI —
export AWS_CA_BUNDLE=~/.config/fogss/server.crt.
Tribes and stores
- Tribe — trust group with a shared 32-byte symmetric key. All tribe data
is encrypted with keys derived from it. Tribe ID =
<slug>-<hash4>. - Store — named bucket inside a tribe, backed by a bare git repo. Each
store has its own S3 credentials (derived from
tribe_key + store_name) and a policy (expose_s3,expose_ssh,auto_replicate,kind). - Store kind —
generic(default, plain S3 bucket) orbitwarden(Vaultwarden-compatible vault; see Frontends below).
All tribe/store commands below talk to the daemon over IPC — run them
in a second terminal while fogss agent start keeps running. The
<hash4> part of a tribe ID is derived from (name, tribe_key) so
copy the actual value from tribe list, don't hand-write it.
fogss tribe create family # derives tribe_id = family-<hash4>
fogss tribe list # prints e.g. "family-ab12 (key: ...)"
# Copy the tribe_id from the line above:
TRIBE=family-ab12
fogss tribe info "$TRIBE"
fogss store create photos --tribe "$TRIBE"
fogss store create vault --tribe "$TRIBE" --kind bitwarden
fogss store list
fogss store s3-credentials photos
S3 API
Endpoint shape is https://<host>:8080/<store>. The path segment is the store
name (not a key prefix).
# Credentials are per store. `s3-credentials` prints both env-var
# exports and an ~/.aws/config/credentials snippet — the grep/sed
# below just lifts the env-var block for `eval`.
eval "$(fogss store s3-credentials photos | grep -E '^\s*export ' | sed 's/^[[:space:]]*//')"
export AWS_CA_BUNDLE=~/.config/fogss/server.crt
aws s3 cp hi.jpg s3://photos/hi.jpg --endpoint-url https://127.0.0.1:8080/photos
aws s3 ls s3://photos/ --endpoint-url https://127.0.0.1:8080/photos
aws s3 cp s3://photos/hi.jpg back.jpg --endpoint-url https://127.0.0.1:8080/photos
Per-store credentials are enforced: keys for store X cannot read store Y.
Presigned URLs (query-string SigV4) and multipart upload work with stock
aws s3 and the official SDKs.
Git / SSH
fogss agent ssh add-key "$(cat ~/.ssh/id_ed25519.pub)"
git clone ssh://git@localhost:2222/photos
cd photos
echo hi > note.txt && git add . && git commit -m 'note' && git push
LFS handles blobs ≥ 1 MiB. Install git-lfs on the client
(apt install git-lfs && git lfs install). git lfs pull fetches content;
pointers clone by default.
Two-device sync (P2P)
Trust is bidirectional — both devices must tribe peer add the other or
nothing syncs.
agent init auto-creates a Default tribe on each device with a fresh
random key — devices need to share a key to sync, so one device creates,
the other joins.
# ── Device A — creates the tribe ────────────────────────────────
fogss agent init # creates identity + local Default tribe
fogss agent start # prompts passphrase (keep running)
# In another terminal:
fogss agent status # note peer_id_A
fogss tribe list # note the derived tribe_id, e.g. default-ab12
fogss tribe info default-ab12 # shows tribe_key (hex)
# ── Device B — joins A's tribe ──────────────────────────────────
fogss agent init
fogss agent start
# In another terminal:
fogss tribe join Default --tribe-key <tribe_key_from_A>
fogss tribe list # now shows A's tribe (default-ab12)
fogss agent status # note peer_id_B
fogss tribe add-peer default-ab12 <peer_id_A>
# ── Device A — closes the loop ──────────────────────────────────
fogss tribe add-peer default-ab12 <peer_id_B>
S3 PUT on A → S3 GET on B within seconds. Same for git push on A →
git clone on B.
CLI surface
Top-level subcommands (see fogss --help for the full list):
fogss agent {init|start|stop|status|lock|unlock|recover|ssh add-key ...}fogss tribe {create|join|list|info|status|add-peer|remove-peer|invite|accept|leave|...}fogss store {create|list|info|delete|s3-credentials|policy|replicate|verify|events|branch ...}fogss tls {init|status|client|list|revoke|export}fogss peer {...}fogss conflict {...}- Data:
fogss put <id> <file> -s <store>,fogss get,fogss list,fogss delete
Frontends
FOGSS stores bytes; frontends give those bytes a purpose. Pick a kind
when you create a store; the matching HTTP surface turns on automatically.
| Kind | What it exposes | Clients |
|---|---|---|
generic |
S3 API (:8080/<store>) and Git SSH (:2222/<store>) |
aws CLI / SDKs, git |
bitwarden |
Vaultwarden-compatible REST at :2204/bw/<tribe>/<store>/ |
bw CLI ≥ 202630, Bitwarden desktop/mobile |
Create a vault-kind store and point a Bitwarden client at it:
fogss store create vault --tribe family-ab12 --kind bitwarden
bw config server https://localhost:2204/bw/family-ab12/vault
bw login --apikey # or full login flow once registration is done in-app
bw list items
The Bitwarden surface is behind the bitwarden Cargo feature
(cargo build --features bitwarden / make release-musl-bitwarden).
Default builds exclude it so the attack surface stays minimal.
More frontend kinds (CardDAV / CalDAV / notes / ...) use the same pattern:
a store of that kind + a protocol adapter module under src/api/<kind>/.
Security model
- Encryption at rest. Every bare git repo under
<data_dir>/git-stores/is wrapped by CPE — blobs, trees and commits are encrypted on disk with keys derived from the tribe key (seesrc/storage/git/encryption.rsandsrc/crypto/). LFS objects get the same treatment. A file-system-level leak (stolen disk, misconfigured backup) reveals ciphertext only.agent.db/runtime.dbare SQLCipher. - Passphrase — Argon2id-derived key unlocks
agent.db(SQLCipher).agent.dbholds identity, BIP39 seed, trusted peers, SSH keys, and the runtime DB key.runtime.db(for audit/events/presence) is unlocked from there and stays open while agent.db is locked. - Auto-lock — session locks after 30 min idle. API calls then 401 until
fogss agent unlock. - Recovery — 24-word BIP39 seed → device keypair via BIP32
(
--device-index). Same seed on multiple devices; each gets a unique index. - Per-store credentials — derived deterministically from
tribe_key + store_name. Compromising one store's creds does not leak another store's data. - Tribe crypto boundary — peers outside the tribe, or holding the wrong
tribe_key, cannot decrypt replicated packs.
Running as a service / on a phone
See docs/DISTRIBUTION.md for the full plan
(Linux systemd, .deb/.rpm, Android APK).
Android: the APK build pipeline is already wired — nix develop pulls
cargo-ndk + NDK 27 + JDK 17 + gradle, and:
make android-debug # arm64 debug APK → android/app/build/outputs/apk/debug/
make android # all-ABI release APK (unsigned)
The Rust daemon is compiled as libfogss.so (see the android feature
in Cargo.toml) and loaded by a minimal Kotlin shell in android/ that
hosts the embedded web UI in a WebView pointed at https://127.0.0.1:2204.
JNI start(passphrase) calls the same commands::start::run entry the
Linux fogss agent start CLI uses, on a dedicated thread with a
current-thread tokio runtime (see android/SKILL.md). Still pending:
BiometricPrompt + Keystore passphrase unlock, cert pinning against
filesDir/fogss/tls/ca.crt, release-signing config — tracked in
docs/DISTRIBUTION.md.
Tests
make test # rust-test + shell-test
make rust-test # curated integration binaries
make shell-test TEST=single-node
cd web && npm test # Playwright (uses system chromium via FOGSS_CHROME)
See tests/TEST_SUITE_USAGE.md and tests/WRITING_TESTS.md before adding
tests — many Rust integration binaries are curated in the Makefile and some
require the QEMU VM bed in vm-work/.
Architecture
See docs/ARCHITECTURE.md for the current layering
(five concurrent surfaces, the Node coordinator, two-DB SQLCipher split,
libp2p transport, etc.).
License
GPLv3.