No description
  • Go 86.6%
  • HTML 8.2%
  • JavaScript 2.5%
  • CSS 2.4%
  • Dockerfile 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Sandeep Vangipuram cd99bda412
All checks were successful
/ print-content (push) Successful in 15s
Add config file logging
2026-09-21 19:19:13 +01:00
.forgejo/workflows Add docker container and CI instructions 2026-09-21 18:41:49 +01:00
cmd/cashboard Initial implementation - from OpenCode 2026-09-19 09:32:48 +01:00
internal Add config file logging 2026-09-21 19:19:13 +01:00
.dockerignore Add docker container and CI instructions 2026-09-21 18:41:49 +01:00
.gitignore Initial implementation - from OpenCode 2026-09-19 09:32:48 +01:00
config.example.yaml Fetch exchange rates from HMRC for reporting 2026-09-21 18:05:35 +01:00
Dockerfile Add config file logging 2026-09-21 19:19:13 +01:00
go.mod Initial implementation - from OpenCode 2026-09-19 09:32:48 +01:00
go.sum Initial implementation - from OpenCode 2026-09-19 09:32:48 +01:00
Makefile Added verbose logging and fixed some invalid endpoints 2026-09-19 10:36:08 +01:00
README.md Fetch exchange rates from HMRC for reporting 2026-09-21 18:05:35 +01:00

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/v1 suffix, e.g. https://app.firefly.example.com/api/v1 (GET /about for health, GET /accounts, GET /transactions, GET /bills).
  • Ghostfolio: the site root, e.g. https://ghostfolio.example.com (the client appends /api/v1/... and authenticates via POST /api/v1/auth/anonymous). Portfolio performance lives under the versioned route GET /api/v2/portfolio/performance?range=1d|ytd|max, which the client calls once per range, and holdings come from GET /api/v1/portfolio/holdings (the top-level /holdings route 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.schedule category map (employment/SA102, self_employment/SA103, pension, property/SA105, interest/SA100 1–3, dividends/SA100 4–6). Category matches are case-insensitive. sa.rates entries 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_default only 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.3 note) 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).