Add native NixOS deployment and UI-managed provider credentials
This commit is contained in:
+122
-10
@@ -36,11 +36,99 @@ The container is unprivileged with a read-only root and a persistent named
|
||||
volume at /data. A host bind mount must be writable by UID/GID 10001.
|
||||
Never change the port mapping to public 0.0.0.0 without VPN/firewall isolation.
|
||||
|
||||
Native NixOS: the flake exports nixosModules.default. Import it into your host,
|
||||
then set services.finance-duck.enable=true and publicURL to the exact browser
|
||||
origin. Deploy with your normal nixos-rebuild switch. The module runs ONE
|
||||
systemd service; React is embedded in the Go binary, not a separate service.
|
||||
Defaults: listen 127.0.0.1:8080, service user finance-duck, persistent data at
|
||||
/var/lib/finance-duck (0700). It does not configure networking or Tailscale.
|
||||
Optional services.finance-duck.environmentFile names an existing runtime file
|
||||
read by systemd for optional provider startup fallbacks. Both Enable Banking
|
||||
and OpenRouter can be configured directly in Settings without that file.
|
||||
Keep private keys outside the Nix store and readable only by the service.
|
||||
See README's native NixOS section for optional environment-file permissions.
|
||||
|
||||
Deployed host: bender@100.87.224.3
|
||||
--------------------------------
|
||||
URL: https://nixos.taile9e6d9.ts.net:8444 (Tailscale only, no application login).
|
||||
The backend binds 127.0.0.1:8080. Existing Funnel on 443 and OpenClaw on 8443
|
||||
are separate. Never enable Funnel for Finance Duck.
|
||||
|
||||
Update from your workstation:
|
||||
ssh -t bender@100.87.224.3 'sudo finance-duck-update'
|
||||
Or on the host:
|
||||
sudo finance-duck-update
|
||||
sudo finance-duck-update --no-pull # deploy existing checkout edits
|
||||
|
||||
The updater serializes updates, pulls ~/projects/finance-duck as bender with
|
||||
git pull --ff-only, creates a Git-filtered source snapshot, updates ONLY the
|
||||
finance-duck input in /etc/nixos/flake.lock, runs nixos-rebuild switch, then
|
||||
checks /api/health with the correct Host. The app has its own pinned Nixpkgs;
|
||||
the host's Nixpkgs and NixVirt pins stay unchanged. A build failure leaves the
|
||||
running service untouched; an activation/health failure exits nonzero.
|
||||
|
||||
Application and deployment source are tracked in the project repository.
|
||||
Commit and push changes before running the normal updater. New local source
|
||||
files must be tracked with git add before a --no-pull deployment. Pull refuses
|
||||
to overwrite conflicting local edits; resolve them before retrying. No
|
||||
automatic stashing, resetting, committing or pushing occurs. Keep credentials
|
||||
and live finance data out of Git.
|
||||
|
||||
Host files:
|
||||
/etc/nixos/flake.nix application input and module import
|
||||
/etc/nixos/modules/finance-duck.nix service, tailnet proxy, update command
|
||||
/var/lib/finance-duck-source replaceable Git-filtered source snapshot
|
||||
/var/lib/finance-duck persistent data, finance-duck:finance-duck 0700
|
||||
The source snapshot excludes .git, ignored files and untracked files. It is
|
||||
not a data backup. The initial deployment starts empty without provider
|
||||
credentials; it does not copy the workstation's finance/ directory.
|
||||
|
||||
Inspect on the host:
|
||||
systemctl status finance-duck finance-duck-tailscale-serve
|
||||
sudo journalctl -u finance-duck -f
|
||||
tailscale serve status
|
||||
curl -f https://nixos.taile9e6d9.ts.net:8444/api/health
|
||||
|
||||
Before significant upgrades, stop the service and back up the entire data
|
||||
directory to a protected location (including hidden recovery files). Restart
|
||||
after the backup, and protect any separate credential files as well:
|
||||
sudo systemctl stop finance-duck
|
||||
sudo install -d -m 0700 /var/backups/finance-duck
|
||||
sudo sh -c 'umask 077; tar -C /var/lib -czf /var/backups/finance-duck/data-$(date +%Y%m%dT%H%M%S).tar.gz finance-duck'
|
||||
sudo systemctl start finance-duck
|
||||
Copy backups to secure off-host storage; a backup on this disk cannot protect
|
||||
against disk loss.
|
||||
|
||||
Binary/system rollback:
|
||||
sudo nixos-rebuild switch --rollback
|
||||
This switches the previous NixOS generation, NOT financial data or config
|
||||
source. The generation before the first deployment has no Finance Duck service.
|
||||
For a lasting rollback, restore the intended source/config/lock before the
|
||||
next update. Do not remove or roll back live data blindly.
|
||||
|
||||
OpenRouter
|
||||
----------
|
||||
Set OPENROUTER_API_KEY in the process environment, then choose a model in
|
||||
Settings. The model and chosen endpoint must support strict structured outputs
|
||||
and the configured privacy routing. Every classification request sets:
|
||||
Open Settings -> OpenRouter credentials, paste the API key, and Save key.
|
||||
Choose the exact provider/model identifier in Classification preferences and
|
||||
save preferences. Replace key rotates it; Remove key disables AI. No SSH,
|
||||
Nix rebuild or restart is needed. Changes affect future classification
|
||||
requests; in-flight requests retain their original key.
|
||||
|
||||
The key is stored as private 0600 plaintext in state/openrouter.json under
|
||||
the data directory, never in config.toml or browser storage. API responses
|
||||
never return the saved key. Backups of the data directory contain this secret.
|
||||
Only allow trusted users to reach the application; there is no separate
|
||||
administrator login for changing credentials.
|
||||
|
||||
OPENROUTER_API_KEY remains a startup fallback only when the saved credential
|
||||
file is absent. A saved key overrides it. Removing the key in Settings saves
|
||||
an explicit disable, which also overrides the environment after restart.
|
||||
Malformed saved credentials fail startup rather than reverting to another key.
|
||||
|
||||
Configured means present, not verified with OpenRouter. A successful
|
||||
AI classification -> Analyse request verifies access with the selected model.
|
||||
The model and endpoint must support strict structured outputs and the
|
||||
configured privacy routing. Every classification request sets:
|
||||
provider.data_collection = deny
|
||||
provider.zdr = true
|
||||
provider.require_parameters = true
|
||||
@@ -71,17 +159,39 @@ Configure the allowed redirect URL to the exact externally reachable URL:
|
||||
https://finance.example.internal/api/banking/callback
|
||||
The browser must be able to reach this callback through your VPN.
|
||||
|
||||
Environment variables (all three required when enabling banking):
|
||||
Open Settings -> Enable Banking credentials. Enter the application ID, upload
|
||||
its matching private-key PEM file, and Save configuration. Settings supplies
|
||||
the exact browser-origin callback URL to register. Keys must be a single
|
||||
unencrypted RSA PKCS#1 or PKCS#8 PEM, >=2048 bits and <=32 KiB.
|
||||
No SSH, environment file, Nix rebuild or restart is needed.
|
||||
|
||||
Save validates local credentials without contacting Enable Banking. It does
|
||||
not register/activate the app or authorize an account. Configured means local
|
||||
setup is present; complete registration above and authorize under Accounts.
|
||||
|
||||
Leave the key upload empty when editing the same application to keep its key.
|
||||
Same-app key/callback rotation preserves sessions and cursors. Changing the
|
||||
application ID requires a new key upload and reconnecting your banks. Remove
|
||||
configuration confirms before disabling banking and clearing local connection
|
||||
state. Canonical accounts/history remain; upstream bank consent is not revoked.
|
||||
Each successful change invalidates pending authorization flows.
|
||||
|
||||
Credentials persist privately (0600) in state/enablebanking.json beneath the
|
||||
data directory. The API never returns the private key; browser storage never
|
||||
contains it. Session state is bound to the application configuration generation,
|
||||
so stale sessions cannot be revived by a restart after app changes/removal.
|
||||
Back up the entire data directory securely, not isolated credential/state files.
|
||||
|
||||
Optional environment startup fallback, used ONLY when no saved config exists:
|
||||
ENABLEBANKING_APP_ID=<registered application ID>
|
||||
ENABLEBANKING_KEY_FILE=/run/secrets/enablebanking.key
|
||||
ENABLEBANKING_REDIRECT_URL=https://finance.example.internal/api/banking/callback
|
||||
Use a PEM RSA private key (PKCS#1 or PKCS#8, at least 2048 bits). Mount it
|
||||
read-only with permissions allowing the service user to read it. Never commit
|
||||
it to Git. The Compose file contains a commented example key mount; set the
|
||||
container-side KEY_FILE path when enabling that mount.
|
||||
The referenced key must be readable by the service. Environment changes need
|
||||
a restart. UI configuration or explicit removal overrides all three variables;
|
||||
invalid saved configuration fails startup rather than using another key.
|
||||
|
||||
Accounts shows a copyable callback URL. Register it exactly with Enable Banking
|
||||
and set ENABLEBANKING_REDIRECT_URL to the same value. The redirect carries a
|
||||
Accounts shows the configured callback URL. Register it exactly with Enable
|
||||
Banking. Settings saves the current browser callback URL. The redirect carries a
|
||||
one-time code and state, not a reusable API key. Go verifies state, exchanges the
|
||||
code for a session_id, and persists session details locally with mode 0600.
|
||||
|
||||
@@ -143,6 +253,8 @@ finance/
|
||||
merchants.finance
|
||||
journal/YYYY/YYYY-MM.finance
|
||||
state/sync-state.json sensitive local consent/session metadata
|
||||
state/openrouter.json sensitive UI-managed OpenRouter key or explicit disable
|
||||
state/enablebanking.json sensitive UI-managed banking key/configuration or disable
|
||||
cache/finance.duckdb disposable analytical projection
|
||||
|
||||
The custom grammar is deliberately small:
|
||||
|
||||
Reference in New Issue
Block a user