deploy/compose/config.yaml
Rama main · réplica de lectura del repositorio original.
# ─────────────────────────────────────────────────────────────────────────────
# Stocktake — configuration
#
# Every option the application understands is listed here. Options that are
# commented out are showing their DEFAULT: uncomment one only to change it.
# Anything left commented behaves exactly as written.
#
# Three places supply configuration, and they win in this order:
#
# 1. environment variables (highest — always wins)
# 2. this file
# 3. the built-in defaults (lowest)
#
# Environment variables use the prefix APP_ and a double underscore between
# levels, so `auth.rate_limit.max_attempts` is APP_AUTH__RATE_LIMIT__MAX_ATTEMPTS.
# That is how secrets reach the application without being written down: the
# database password arrives as APP_DATABASE__PASSWORD and never appears here.
#
# NEVER put a secret in this file.
#
# Most of these can also be changed from Admin → Settings inside the
# application, which writes back to this file and keeps these comments. If the
# file is supplied read-only — a Kubernetes ConfigMap, a read-only volume, or
# baked into an image — that page shows the values but cannot save them, and
# says so.
#
# A mistyped KEY here stops the application at startup rather than being
# ignored, which is deliberate: a silently ignored setting is the failure
# nobody notices. (A mistyped environment variable is simply not seen — the
# asymmetry is worth knowing.)
# ─────────────────────────────────────────────────────────────────────────────
# The application's name. Also decides the default database filename, so
# changing it points the app at a different database.
app_name: stocktake
# How much detail reaches the log. DEBUG | INFO | WARNING | ERROR.
# Takes effect at startup, so a change needs a restart.
# log_level: INFO
# Where this portfolio lives, as an IANA timezone name.
#
# EVERY date decision is made in this zone: whether a trade is in the future,
# which financial year is current, where a performance window starts, what the
# calendar highlights. Containers run in UTC, so leaving this wrong makes the
# application a day behind for the first hours of each morning — and the new
# financial year arrive a day late. Timestamps (session expiry, audit fields)
# are stored in UTC regardless; only calendar decisions use this.
timezone: Australia/Melbourne
# ── Database ────────────────────────────────────────────────────────────────
# Only ever changed here — the settings page shows it read-only, because
# editing it from a web form is a way to lose a database. Keep `type:` set:
# a `database:` heading with nothing under it stops the app at startup.
database:
# sqlite (default) or postgres.
#
# SQLite needs nothing else running and is the right choice for one
# household. Postgres is worth it if you already run one, for many people
# writing at once, or for a database into the gigabytes. Either way the app
# runs as one replica: the import preview, the statement mapper and the price
# feed each live in its one process.
type: sqlite
# Where the SQLite file lives. MUST be on a mounted volume that survives a
# restart — a container's own filesystem does not. Defaults to
# /data/<app_name>.db.
path: /data/stocktake.db
# For `type: postgres` instead of `path` (name and user are required, and
# the password should come from APP_DATABASE__PASSWORD, never from here):
# host: postgres
# port: 5432
# name: stocktake
# user: stocktake
# sslmode: disable
# ── Prices and exchange rates ───────────────────────────────────────────────
# Daily closes from Yahoo Finance. Only ticker SYMBOLS ever leave this machine
# — never your holdings, quantities or balances. Runs once at startup when the
# data is stale, then daily; also runnable by hand with
# `python -m app.pricefeed`.
price_feed:
# Turn the feed off to freeze valuations at the last stored close.
# Takes effect at startup, so a change needs a restart.
# enabled: true
# When the daily run happens, in the timezone above. Set it after the market
# you follow has closed.
hour: 18
minute: 30
# How far back to fetch when an instrument is first seen. Earlier means a
# longer first run and a longer chart. Defaults to 2020-01-01. Exchange
# rates also reach back to your first trade in each currency.
# backfill_start: 2020-01-01
# The feed follows the top-level `timezone` unless you set this. Only worth
# setting if the market you track keeps different hours from the zone you
# count your days in.
# timezone: Australia/Melbourne
# ── Live prices ──────────────────────────────────────────────────────────
# The daily run above records what each day CLOSED at. This shows what a
# holding is worth right now, so the app is not stuck on yesterday all day.
#
# Live prices go into the same table flagged `provisional`, and the daily run
# replaces them with the real close once the market makes one — so what is
# kept as history is only ever closing prices.
#
# It costs almost nothing: one batched request covers every holding, and it
# runs only while somebody is signed in AND an exchange you hold is trading.
# Nobody logged in, or every market shut, and it makes no requests at all.
# quotes_enabled: true
# quote_interval_minutes: 5
# ── Sign-in ─────────────────────────────────────────────────────────────────
auth:
# How long a signed-in session lasts. It slides: every request pushes the
# expiry out again.
# session_ttl_days: 30
# Two independent limits the sliding renewal cannot extend. Idle is how long
# a session survives with no requests; absolute is the cap however active it
# is, and it is what a stolen cookie eventually runs into.
# session_idle_minutes: 60
# session_absolute_days: 7
# How long before the idle cutoff the browser warns and blurs the figures.
# idle_warning_seconds: 120
# Send the session cookie only over HTTPS. Turn this ON once the application
# is behind TLS. Leave it off on plain HTTP or nobody can sign in.
# cookie_secure: false
# The session cookie's name. Worth changing only if it collides with
# something else on the same domain.
# cookie_name: pf_session
# Which peers may be believed when they send X-Forwarded-For or
# X-Forwarded-Proto, as addresses or CIDR ranges. EMPTY BY DEFAULT, meaning
# never: anyone can set those headers, so trusting them from an arbitrary peer
# lets a caller choose the address recorded against their sign-ins, or claim
# an encrypted connection that is not. Set this to your reverse proxy once
# there is one in front: without it, a proxy that terminates TLS makes every
# request look like plain HTTP, so the login page warns about it and passkeys
# are not offered.
# trusted_proxies: []
# - 10.42.0.0/16
# Lockout after repeated failures, counted per account from every source
# address together, so guesses spread across many machines still add up.
# Anyone who knows an account's email can therefore keep it locked by
# failing on purpose; passkeys and single sign-on are not affected. This
# covers the two-factor code stage as well as the password, so a six-digit
# code cannot be brute-forced by someone who already has the password.
# rate_limit:
# max_attempts: 8 # failures before the lockout starts
# window_minutes: 15 # how long failures are remembered
# lockout_minutes: 15 # how long the wait lasts
# ── Passkeys and security keys — RESERVED, nothing reads this yet ─────────
# Two things must be true before WebAuthn can be switched on:
#
# 1. HTTPS. WebAuthn requires a secure context, so http://stocktake.home
# cannot do it. That is a browser rule, not a limit of this app.
# Public reachability is NOT required — an internal-only hostname with
# DNS-01 certificates works perfectly well.
# 2. A settled canonical domain. A credential binds permanently to the
# rp_id it was registered under, so a passkey enrolled at
# stocktake.example.com will not work from stocktake.home. Decide the
# name BEFORE anyone enrols.
#
# rp_id is declared here and never read from the Host or X-Forwarded-Host
# header: those are client-controlled, and rp_id is the anchor every
# credential is bound to.
#
# If you terminate TLS at a Cloudflare tunnel, note that Cloudflare sees your
# traffic in plaintext at its edge — fine for many people, worth knowing for
# an app that shows everything you own.
# webauthn:
# enabled: false
# rp_id: stocktake.example.com
# rp_name: Stocktake
# origins:
# - https://stocktake.example.com
# ── Single sign-on (OIDC) ──────────────────────────────────────────────────
# Hand sign-in to an identity provider you already run. Off by default, and
# inert when off: nothing is requested of anybody until somebody presses the
# button. Local passwords keep working, so a provider that is down is an
# inconvenience rather than a lock-out.
#
# provisioning decides who may sign in:
# 'off' — only accounts already linked to this provider
# 'invite' — those, plus anyone holding a live invitation link
# 'open' — anyone your provider authenticates
# Quoted because YAML reads a bare off as false.
#
# Accounts match on the provider's sub claim and never on the email address:
# an address is something a directory can be told, so matching on it would be
# a way to sign in as somebody else.
#
# redirect_uri must match what the provider has registered, exactly. It is
# declared here rather than read from the request, for the same reason as
# rp_id above.
#
# client_auth is basic (send client_secret) or none (a public client, with
# PKCE alone). It is stated rather than inferred from a missing secret, so a
# secret dropped from the environment refuses rather than quietly going
# anonymous.
# oidc:
# enabled: false
# issuer: https://auth.example.com/application/o/stocktake/
# client_id: stocktake
# client_auth: basic
# client_secret: from-your-provider
# redirect_uri: https://stocktake.example.com/login/oidc/callback
# button_label: Sign in with your identity provider
# provisioning: 'off'
# scopes: [openid, email, profile]
# ── Metrics ─────────────────────────────────────────────────────────────────
# A Prometheus endpoint at /metrics, off by default. It publishes machine
# health only — request counts, feed freshness, session numbers. No holdings,
# no values, no tickers.
# metrics:
# enabled: false
# ── Housekeeping ────────────────────────────────────────────────────────────
# A daily sweep, on its own schedule rather than riding along with the price
# feed — turning the feed off should not silently stop expiring sessions.
# It removes expired sessions, forgets old failed sign-ins, and clears
# half-finished imports left in the staging area.
# maintenance:
# enabled: true
# hour: 3
# minute: 30
# attempt_retention_days: 30 # failed sign-ins are kept only to drive lockout
# staged_upload_hours: 24 # a half-finished import holds real trade data
# ── Optional features ───────────────────────────────────────────────────────
# Parts of the app an install can leave out. Every one defaults ON, so an
# upgrade never removes something that was there. Switching one off hides its
# pages and refuses its routes; it does NOT delete anything, so turning it back
# on restores what was there.
#
# The setup wizard writes this block, and Admin → Settings edits it.
# features:
# dca_schedule: true # scheduled buys, the rotation, and the calendar
# ── Broker imports ──────────────────────────────────────────────────────────
imports:
# Whether a broker CSV may create instruments it does not recognise. On by
# default: the preview lists every new ticker with its exchange for you to
# correct before anything is written. Off refuses the import instead.
# allow_new_instruments: true
# Largest upload accepted, in megabytes. A broker CSV or a statement PDF is
# a few hundred kilobytes. This bounds what the application reads into
# memory; limiting what a client can SEND is a job for the proxy in front.
# max_upload_mb: 10
# Where saved broker and statement layouts are kept, and where the visual
# mapper and a CSV awaiting its commit keep their working files. All are
# server paths rather than preferences; change them only if those
# directories are not writable.
# templates_dir: /data/formats
# visual_dir: /scratch/visual
# staging_dir: /scratch/staged
# Reading statements that arrive as SCANS — a photograph of a page wrapped in
# a PDF, with no text in it. On by default; where the tesseract program is
# missing the app says so per upload and carries on.
#
# The work is entirely local. Tesseract is a binary in the container with no
# network of its own, and there is no cloud OCR option here — a dividend
# statement carries your name, your address and your holder number.
#
# Turn it off if you would rather no subprocess ran over an uploaded file.
# You are still told why a scan produced nothing; that never depends on this.
# ocr:
# enabled: true
# Broker CSV formats are FILES now, in app/formats/brokers/ — they ship with
# the app, are tested in CI, and a new one is a pull request rather than a
# block in one person's config. The interface can install one too.
#
# This key still exists to OVERRIDE a shipped format by name, for the case
# where your broker's export differs from the one that was verified. Anything
# here wins over the file, so do not paste a copy of a shipped format in:
# that pins it forever and a later fix to the file would never reach you.
#
# brokers:
# selfwealth:
# kind: mapped # or commsec_transactions
# exchange: ASX
# currency: AUD
# date_format: "%d/%m/%Y"
# columns: # the fields the importer needs -> your headings
# date: Trade Date
# action: Buy/Sell
# ticker: Code
# units: Units
# price: Price
# brokerage: Brokerage