Skip to content

Latest commit

 

History

History
180 lines (132 loc) · 5.31 KB

File metadata and controls

180 lines (132 loc) · 5.31 KB

Setup guide

Requirements

  • Node.js 22+ (tested with v22.20.0)
  • Claude Code with an active Claude subscription (claude command in PATH) for AI features
  • Optional: a YNAB account with a personal access token, only if you want to connect YNAB
  • SQLite (bundled via better-sqlite3, no separate install)

Installation

git clone git@github.com:rollecode/dough.git
cd dough
npm install

Configuration

Copy the example env file:

cp .env.local.example .env.local

Edit .env.local:

  • SESSION_SECRET — random string for JWT signing
  • DOUGH_ENCRYPTION_KEY — optional, 32 random bytes (openssl rand -hex 32). When set, bank sync, YNAB and AI credentials are encrypted in the database. Run npx tsx scripts/seal-secrets.ts once to encrypt the ones already saved. Keep the key: without it those credentials cannot be read and have to be entered again
  • YNAB_ACCESS_TOKEN — optional, can be set via settings UI instead
  • YNAB_BUDGET_ID — optional, can be set via settings UI instead
  • YNAB_CLIENT_ID, YNAB_CLIENT_SECRET — optional. With an OAuth app registered at app.ynab.com (Developer settings, redirect address https://your-dough/api/ynab/oauth/callback), Settings offers Sign in with YNAB instead of pasting a personal token, and the token renews itself. YNAB_REDIRECT_URI overrides the redirect address when one address serves several instances
  • CLAUDE_PATH — path to claude CLI binary, defaults to claude in PATH

Create users

Set env vars and run the seed script:

USER1_EMAIL=yourname USER1_PASSWORD=yourpassword USER1_NAME="Your Name" \
USER2_EMAIL=partner USER2_PASSWORD=partnerpassword USER2_NAME="Partner" \
npx tsx scripts/seed.ts

To add one person later, without putting the password on the command line:

echo '{"email":"you@example.com","name":"You","locale":"en","password":"..."}' | npx tsx scripts/create-user.ts

After an upgrade, npx tsx scripts/migrate.ts data/dough.db brings a database up to date without starting the app.

Demo data

To try the app, or to take screenshots, without using real finances:

npx tsx scripts/seed-demo.ts
DOUGH_DB_PATH=data/dough-demo.db npm run dev

The script writes data/dough-demo.db and refuses to touch data/dough.db. Everything in it is invented: twelve months of transactions, bills, subscriptions, income, categories and balances, all generated from a fixed seed so repeated runs produce the same database. Sign in with demo@example.com and demo1234.

DOUGH_DB_PATH points the app at any database file, so the real one stays where it is.

Build and run

npm run build
npm start -- -p 3001

The app runs at http://localhost:3001.

First login

  1. Log in with the credentials you set in the seed script
  2. Set your name, household size, and add your accounts (or connect YNAB - see below)
  3. Link your spending account so the daily budget knows what you pay from
  4. Add income sources and recurring bills, and set up budget categories and targets
  5. Optionally connect Synci to import bank transactions automatically (see the timer section below)

Dough chooses its mode automatically: without a YNAB token and budget it runs standalone (its own accounts, transactions, and envelope budgeting); with them it mirrors YNAB. To connect YNAB, paste a personal access token in settings (or .env.local) and select your budget.

Cloudflare tunnel (optional)

To expose the app publicly:

cloudflared tunnel create dough
cloudflared tunnel route dns dough your-domain.example.com

Create a config file at ~/.cloudflared/config-dough.yml:

tunnel: <tunnel-id>
credentials-file: ~/.cloudflared/<tunnel-id>.json
ingress:
  - hostname: your-domain.example.com
    service: http://localhost:3001
  - service: http_status:404

Systemd services (optional)

Create user services for auto-start:

# ~/.config/systemd/user/dough.service
[Unit]
Description=Dough personal finance app
After=network-online.target
Wants=network-online.target

[Service]
WorkingDirectory=/path/to/dough
ExecStart=/path/to/node node_modules/.bin/next start -p 3001
Restart=on-failure
RestartSec=2
TimeoutStopSec=5
KillMode=mixed
SuccessExitStatus=143
Environment=NODE_ENV=production

[Install]
WantedBy=default.target
systemctl --user enable dough
systemctl --user start dough

Periodic Synci sync (systemd timer)

Synci sync is idempotent (it skips anything already imported or added manually), so it is safe to run on a timer. Set a cron_secret household setting, then install a service + timer that polls it:

# ~/.config/systemd/user/dough-synci.service
[Unit]
Description=Dough Synci sync

[Service]
Type=oneshot
ExecStart=/usr/bin/curl -fsS -X POST http://127.0.0.1:3001/api/synci/sync -H "X-Cron-Secret: YOUR_CRON_SECRET"
# ~/.config/systemd/user/dough-synci.timer
[Unit]
Description=Run Dough Synci sync every 30 minutes

[Timer]
OnBootSec=5min
OnUnitActiveSec=30min
Persistent=true

[Install]
WantedBy=timers.target
systemctl --user enable --now dough-synci.timer
systemctl --user list-timers dough-synci.timer   # verify it is scheduled

Backups

The SQLite database lives at data/dough.db. Back it up regularly:

sqlite3 data/dough.db ".backup /path/to/backup/dough-$(date +%Y%m%d).db"