Raise a job against a unit, cost it, close it — and settle move-out damage with ingoing/outgoing inspection comparison instead of an argument. One static binary, one SQLite file. No cloud account, no subscription, no external service.
Quick start · Screenshots · How it works · Status · Docs
Vulos — rooted in vula, the Zulu and Xhosa word for open.
Maintenance and inspection software for people who look after property — managing agents, landlords with a portfolio, body corporates, facilities teams.
It runs as one binary and one SQLite file. A tablet, a laptop, an office NAS or a Raspberry Pi is a complete deployment. Copy the file, run it, done.
Two things it does that general ticketing tools do not:
Costs are per door, not per ticket. Every job belongs to a unit, and spend
and labour aggregate per building and per unit. Units are real records with a
normalised key, so Flat 3A, 3A and flat 3a are one door rather than three
rows quietly fragmenting your reporting.
Inspections compare. Run an ingoing condition report at move-in and an outgoing one at move-out, and PropFix diffs them item by item — what changed, in which direction, and what degraded. Damage liability becomes evidence rather than a disagreement.
Light and dark shots of every surface are in docs/SCREENSHOTS.md. All are captured from the real binary in demo mode by npm run screenshots — nothing is mocked up.
git clone https://github.com/vul-os/propfix && cd propfix
npm install && npm run build # build the app
bash scripts/build-embedded.sh # compile it into the binary
./backend/propfix --demo # http://localhost:8080Demo mode runs entirely in memory with a seeded portfolio — no database, no
configuration, nothing written to disk. Sign in as demo@propfix.local /
demopassword.
For a real deployment:
./backend/propfix --db /var/lib/propfix/propfix.db --addr 0.0.0.0:8080The first account you register becomes the owner; registration then closes, and further accounts are created by an authenticated operator. See SELFHOST.md.
flowchart LR
subgraph "One binary"
API["Go API<br/><i>chi</i>"]
DB[("SQLite<br/><i>+ oplog</i>")]
UI["React app"]
SITE["Docs & landing"]
end
P2["Another site<br/><i>office · contractor</i>"]
USB["Shared folder<br/><i>NAS · USB stick</i>"]
UI --> API --> DB
API <-->|"signed ops<br/>Ed25519"| P2
API <-->|"ops-<node>.jsonl"| USB
The building is the authority. Whoever manages a building owns its jobs, its job numbering and its inspections. Because the only contended decision — who does the work — has exactly one legitimate writer, there is no consensus protocol, no leader election and no distributed lock anywhere in the system.
Money and hours are append-only. A job's cost is SUM over its ledger at
read time, never a stored column. Two people costing the same job while
offline therefore add rather than overwrite, and a correction is a negative
entry, so the audit trail is complete by construction.
Peers are enrolled by hand. No discovery, no rendezvous, no hub. An
operator enters another node's URL; requests are mutually signed with Ed25519.
For sites with no connectivity, each node appends only its own
ops-<node>.jsonl to a shared folder — so a NAS or a USB stick is a valid
transport with no possibility of a write conflict.
Honest per-area accounting. A feature that silently does nothing is worse than one that says it is not built.
| Area | State |
|---|---|
| Jobs — raise, triage, assign, cost, close | Built |
| Buildings and units, unit normalisation | Built |
| Append-only cost and time ledgers | Built |
| Reports — per building, per unit | Built |
| Inspections — condition capture, completion | Built |
| Ingoing/outgoing comparison | Built |
| Auth, org scoping, first-run registration | Built |
| Peer sync — HTTP transport, Ed25519 envelopes, folder/USB | Built, not yet exercised beyond its test suite |
WRAP trades/v0 binding |
In progress — cross-organisation work orders |
| Tenant portal | Designed, not built. The data model carries public vs internal event visibility; there is no tenant-facing surface yet |
| Photo attachments | Partial — references exist; content-addressed blob storage and replication are not built |
| Template versioning | Open question. Comparison tolerates template drift by pairing on item text; there is no formal versioning |
| Recurring / planned maintenance | Not built |
| Compliance certificates | Not built |
| Flag | Default | Description |
|---|---|---|
--db |
propfix.db |
SQLite file path |
--addr |
:8080 |
Listen address |
--demo |
off | In-memory demo data; forces :memory: so it can never touch a real database |
--sync-listen |
off | Accept sync from enrolled peers |
--sync-peer |
— | Peer URL to sync with |
--sync-folder |
— | Shared directory for file transport |
Every networked feature is off by default. A fresh install makes no outbound connections.
| Doc | |
|---|---|
| Architecture | The binding contract — read before changing anything structural |
| Getting started | Install and first run |
| Configuration | Flags and settings |
| Sync | The replication protocol in depth |
| WRAP | Cross-organisation work orders |
| Inspections | Condition capture and comparison |
| Self-hosting | Deployment |
| Threat model | Including what is not protected |
| FAQ |
npm run dev # frontend on :5173
npm run build # production bundle
npm test # vitest
npm run test:e2e # playwright, against the real binary
npm run screenshots # regenerate docs/screenshots from demo mode
make check # the full gate
cd backend && go test ./...Issues and pull requests welcome. Read ARCHITECTURE.md first — several of its rules (append-only money, building-as-authority, author-key tie-breaks) look like style preferences and are not.
MIT OR Apache-2.0 — © VulOS. PropFix is a VulOS project; source and issues at github.com/vul-os/propfix.
![]()
vulos — open by design





