- Go 86.6%
- HTML 8.2%
- JavaScript 2.5%
- CSS 2.4%
- Dockerfile 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo/workflows | ||
| cmd/cashboard | ||
| internal | ||
| .dockerignore | ||
| .gitignore | ||
| config.example.yaml | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| Makefile | ||
| README.md | ||
cashboard
A personal finance dashboard served over HTTP. It presents two tabs:
- Overview — a single-glance page combining cash/accounts from Firefly III and investments from Ghostfolio, with charts (Chart.js, vendored) and tables.
- Self Assessment — UK tax return figures for a user-choosable tax year (6 April–5 April), laid out to mirror the SA100 form sections plus an estimated tax bill.
Requires Go 1.27+. No JavaScript build step.
Run
cp config.example.yaml config.yaml # fill in your tokens
go run ./cmd/cashboard
Open http://localhost:8080. GET /healthz returns ok.
Configuration
| Env var | Purpose |
|---|---|
CASHBOARD_CONFIG |
Path to a YAML/JSON config file |
CASHBOARD_LISTEN |
Listen address (default :8080) |
CASHBOARD_FIREFLY_URL |
Firefly III API base URL |
CASHBOARD_FIREFLY_TOKEN |
Firefly III personal access token |
CASHBOARD_FIREFLY_VERSION |
v1 (implemented) or v2 (not yet) |
CASHBOARD_GHOSTFOLIO_URL |
Ghostfolio base URL |
CASHBOARD_GHOSTFOLIO_TOKEN |
Ghostfolio access token |
CASHBOARD_TAX_YEAR |
Default SA tax year label, e.g. 2025-26 |
CASHBOARD_ENV_FILE |
Path to the .env file to load (default ./.env) |
CASHBOARD_DEBUG |
true to enable verbose HTTP tracing (same as server.debug: true) |
Config file search order: CASHBOARD_CONFIG, ./config.yaml, ./config.json.
Any key can be omitted to keep defaults (Firefly URL/token are required).
Verbose HTTP logging
Set server.debug: true (or CASHBOARD_DEBUG=true) to log every outbound
Firefly III and Ghostfolio request: method, full URL, response status, and the
response body on errors. No secrets are logged (Authorization headers and the
Ghostfolio JWT are never printed; the access-token length is shown instead).
This is the first thing to enable when debugging 401/404 errors:
CASHBOARD_DEBUG=true go run ./cmd/cashboard
Both clients expect the API root in base_url:
- Firefly: include the
/api/v1suffix, e.g.https://app.firefly.example.com/api/v1(GET /aboutfor health,GET /accounts,GET /transactions,GET /bills). - Ghostfolio: the site root, e.g.
https://ghostfolio.example.com(the client appends/api/v1/...and authenticates viaPOST /api/v1/auth/anonymous). Portfolio performance lives under the versioned routeGET /api/v2/portfolio/performance?range=1d|ytd|max, which the client calls once per range, and holdings come fromGET /api/v1/portfolio/holdings(the top-level/holdingsroute does not exist in Ghostfolio).
.env parsing: a file at ./.env (or CASHBOARD_ENV_FILE) is loaded before
other env handling. Precedence is defaults < config file < .env < real
environment; a variable that already exists in the environment is never
overwritten by the .env file. Lines are KEY=value (optional export and
matching quote stripping supported, # comments ignored). .env is already
git-ignored.
Self Assessment
Data sources:
- Firefly III transactions are mapped onto SA worksheets via the
sa.schedulecategory map (employment/SA102,self_employment/SA103,pension,property/SA105,interest/SA100 1–3,dividends/SA100 4–6). Category matches are case-insensitive.sa.ratesentries in a config file merge over the built-in defaults, so a config listing only one tax year still works for the others; a listed year overrides that year's defaults. - Ghostfolio dividends/interest feed the dividend and interest sections; sell
orders are used for capital gains. Accounts listed in
sa.ghostfolio_untaxed_accounts(e.g. an ISA or SIPP) have their gains separated out of the taxable SA108 section and shown as exempt. - Currency is preserved for every transaction, Firefly or Ghostfolio.
When the SA base currency is GBP, foreign-currency amounts are converted to
sterling at the HMRC monthly exchange rate for the relevant transaction
month, and the sterling figure feeds the boxes and the bill:
- foreign dividends → SA100 Box 6, foreign interest → Box 3 (converted via HMRC rates),
- foreign employment/self-employment/pension/property income → their respective boxes,
- foreign capital-gains disposals are pooled in their own currency (Section
104) and each disposal is converted at the sale-month rate; the
converted proceeds, cost and gain appear in the "Foreign currency capital
gains" section.
Rates are fetched from
trade-tariff.service.gov.uk/exchange_rates/view/files/monthly_csv_*and cached in Firefly III (exchange-rates/{GBP}/{code}), consulted first on subsequent runs.sa.dividends_uk_by_defaultonly decides the box for base-currency dividends.
- If no rate is available for a currency/month (e.g. Firefly miss and an HMRC fetch failure), that amount stays as-is in its own currency, is excluded from its box, from the converted "Foreign currency..." totals and from the bill estimate (unconverted foreign disposals still appear in the section with their own symbol); a notice is added to the return explaining what could not be converted, so you can convert manually at the HMRC annual rate when filing. A month with any unconverted item is not written back to the Firefly cache.
- Foreign-currency amounts are shown with their own symbol (e.g.
$71,816) alongside the converted sterling figure (≈column /@ 1.3note) when a rate was available; the sterling value is what goes in the boxes.
Tax bill estimate
The bill is indicative only and assumes the defaults in the config:
- Statutory ordering: personal allowance against non-savings → savings → dividends; savings starting rate (£5,000) then PSA by adjusted net income; dividend allowance.
- Interest is used exactly as reported by the source transactions, with no gross-up. Tax deducted at source is the sum of the actual transactions mapped to a "tax" category (PAYE, tax on pensions, self-employment payments); there is no calculated deduction.
- Capital gains are taxed as non-residential assets (10%/20% base rates using the unused basic band), losses net off gains, and the annual exempt amount is applied.
- Does not model gift aid, pension contributions, marriage allowance, Scottish/Welsh rates, or other reliefs. Not a tax opinion.
Capital gains pooling
Section 104 pooling only. Same-day and 30-day bed-and-breakfast matching is
not modelled; buys and sells are pooled chronologically by account +
symbol + data source, which is accurate for most long-term portfolios.
Disposals from accounts in sa.ghostfolio_untaxed_accounts (ISA/pension) are
pooled separately and excluded from the taxable capital gains and the bill
estimate.
Development
go test ./... # unit + render smoke tests
go vet ./...
go build ./... # or go build ./cmd/cashboard
Layout:
cmd/cashboard— entrypoint.internal/config,internal/money— config + integer-pence money.internal/firefly,internal/ghostfolio— API clients.internal/dashboard— overview aggregation.internal/sa— tax years, category roll-up, Section 104 pooling, bill.internal/web— HTTP server, templates, static assets (vendored Chart.js).