CiviCRM-to-UniFi-Access reconciliation service for door access control. Runs on a Raspberry Pi as a systemd service.
In active development. Pure modules, CiviCRM client, UniFi Access client, orchestrator + ops stubs, and the scheduler daemon loop are merged. A real alert transport (SMTP/webhook) is the remaining slice before v1.
- Architecture Reference — internal module layout, data contracts, pure/impure boundaries, coding conventions
Requires Python 3.11+ and uv.
uv sync # install
uv run pytest # tests
uv run pyrefly check # type check
uv run ruff check . # lintuv run door-sync run # daemon: loop until SIGTERM/SIGINT
uv run door-sync run --dry-run # daemon, but compute-only (no UniFi writes)
uv run door-sync run --once # one reconcile cycle, then exit
uv run door-sync run --once --dry-run # one cycle, compute-only
uv run door-sync show-diff # read-only: print the computed diff
uv run door-sync validate-config # load config, print issues, exit 0/1Dry-run is safe to point at production data.
deploy/door-sync.service is a systemd unit template. To install:
- Install the CLI:
uv tool install --from /path/to/door-sync-checkout door-sync(or build a wheel withuv buildand runuv tool install ./dist/door_sync-*.whlon the Pi). - Create the service user:
sudo useradd --system --no-create-home door-sync. - Create the config and ops directories:
sudo mkdir -p /etc/door-sync /var/log/door-sync /var/lib/door-sync /var/run/door-sync sudo chown -R door-sync:door-sync /var/log/door-sync /var/lib/door-sync /var/run/door-sync
- Drop
config.tomlinto/etc/door-sync/(mode 0644) andenvinto the same dir (mode 0400). - Install the unit:
sudo cp deploy/door-sync.service /etc/systemd/system/. - Start:
sudo systemctl daemon-reload && sudo systemctl enable --now door-sync.
Stop with sudo systemctl stop door-sync; the daemon catches SIGTERM, finishes its in-flight reconcile, and exits 0.
Two files:
- Env file for secrets (API keys only). Dev:
.env. Prod:/etc/door-sync/env, mode0400. - TOML file for everything else (host URLs, TLS fingerprint, tier mapping, thresholds, cadence). Dev:
config.toml. Prod:/etc/door-sync/config.toml.
See config.example.toml and .env.example for the full schema. Copy them and fill in real values.
src/door_sync/
config.py # env + TOML loading
models.py # dataclasses
tier_mapping.py # resolution rules (PURE)
reconciler.py # diff computation (PURE)
safety.py # guards (PURE)
civicrm/ # API client
unifi/ # API client (read + write + dry-run)
orchestrator.py # reconcile() — the wiring
audit.py # append-only JSONL log
state.py # last-success/halt JSON file
alert.py # flag-file alert stub (SMTP/webhook TBD)
cli.py # pretty-printers for show-diff / validate-config
__main__.py # argparse + subcommand dispatch
scheduler.py # daemon loop with SIGTERM/SIGINT handling
TBD.