Ctrl K
ComunidadGuías…

docs/reference/configuration.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Configuration reference

Every setting, its default, and what it does.

Configuration comes from three places, in this order of priority:

1. **Environment variables** — `APP_` prefix, `__` between levels.
   `APP_DATABASE__PASSWORD`, `APP_AUTH__COOKIE_SECURE=true`.
2. **The YAML file** at `APP_CONFIG_FILE`.
3. **Built-in defaults.**

**Secrets belong in environment variables, never in the file.** The database
password is read from `APP_DATABASE__PASSWORD` so it can come from a Docker
secret or a Kubernetes Secret without ever being written to disk in the clear.

!!! tip "The file is optional"
    Every setting has a default, so the app starts with no configuration file
    at all — and when it does, it boots into the
    [setup wizard](../getting-started/index.md), which writes one for you from
    the annotated default below.

    That is also why the wizard offers a database step only when nothing else
    has chosen one. If `APP_DATABASE__*` is set, or the file already has a
    `database:` block, the wizard reports what it found and moves on: the
    process is already connected to it, and offering to change it would be
    offering to lose a database. The password, port and SSL mode say how to
    reach a database, not which one, so set alone they leave the step offered;
    the wizard then tests and saves with that password, and keeps it out of
    the file.

!!! warning "Unknown keys are rejected"
    A key the app does not recognise is a startup error, not a warning — a
    typo in a setting should be loud rather than silently ignored. It also
    means **the config file and the application version are a matched pair**:
    a file written for a newer version will be rejected by an older one, and
    vice versa. Move them together.

## The annotated file

This is the shipped `config.yaml`. Every option appears, commented out at its
default — uncomment what you want to change.

```yaml
# ─────────────────────────────────────────────────────────────────────────────
#  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

  # ── 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 are stored beside the closes, flagged as provisional, and the
  # daily run replaces each with the real close once its market makes one — so
  # the history keeps only closing prices.
  #
  # One batched request covers every holding, and it runs only while somebody
  # is signed in AND an exchange you hold is trading. Nobody signed in, or
  # every market shut, and it makes no outbound requests at all.
  # quotes_enabled: true
  # quote_interval_minutes: 5

  # 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

# ── Sign-in ─────────────────────────────────────────────────────────────────
auth:
  # How long a signed-in session lasts. It slides: every request pushes the
  # expiry out again.
  # session_ttl_days: 30

  # Two timeouts, and the shorter one wins. `session_idle_minutes` is time
  # since you last DID something — background polling deliberately does not
  # count, or a tab left open would keep itself alive forever.
  # `session_absolute_days` is a hard ceiling nothing can push out, which is
  # what bounds a stolen cookie.
  # session_idle_minutes: 60
  # session_absolute_days: 7

  # How long before the idle timeout the page starts warning, in seconds.
  # The overlay counts down and offers to stay signed in.
  # 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. Use the proxy's own address or range, never the network
  # your browsers are on, or their claims get believed as if a proxy had
  # written them.
  # 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 ────────────────────────────────────────────
  # Once set, a passkey appears as an option on the login page and can be
  # added from your account. It signs you in on its own: the ceremony requires
  # a fingerprint, face or PIN, so it stands in for the password AND the
  # authenticator code.
  # 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.
  # ── Single sign-on (OIDC) ─────────────────────────────────────────────────
  # Hand sign-in to an identity provider you already run — Authentik, Authelia,
  # Keycloak, Pocket ID. Off by default, and inert when off: no request is made
  # to anybody until somebody presses the button.
  #
  # Local passwords keep working. That is deliberate: an IdP that is down
  # should be an inconvenience, not a lock-out from your own records.
  #
  # `provisioning` decides who may sign in:
  #   off    — only accounts already linked to this provider
  #   invite — an unknown person may create an account, but only holding a
  #            valid invite from a portfolio owner
  #   open   — anyone your provider authenticates gets an account
  #
  # Accounts are matched on the provider's `sub` claim, never on the email
  # address: an address is something a directory can often be told, and
  # matching on it would be a way to take over an existing account.
  #
  # 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
  # webauthn's rp_id — a value taken from a header is one the caller chose.
  # oidc:
  #   enabled: false
  #   issuer: https://auth.example.com/application/o/stocktake/
  #   client_id: stocktake
  #   client_auth: basic            # basic sends client_secret; none = public client
  #   client_secret: from-your-provider
  #   redirect_uri: https://stocktake.example.com/login/oidc/callback
  #   button_label: Sign in with Authentik
  #   provisioning: 'off'
  #   scopes: [openid, email, profile]

  # webauthn:
  #   enabled: false
  #   rp_id: stocktake.example.com
  #   rp_name: Stocktake
  #   origins:
  #     - https://stocktake.example.com

# ── 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

# ── Metrics ─────────────────────────────────────────────────────────────────
# A Prometheus endpoint at /metrics. OFF by default: every other route needs a
# session, and a scraper has none, so this is the one anonymous endpoint in the
# app and turning it on should be a decision.
#
# What it publishes is the application's own health — when prices were last
# fetched, whether the last fetch worked, whether the database answers. It says
# nothing about holdings, values or trades, and a test keeps the list closed so
# it stays that way.
#
# Ready-made alert rules live in deploy/monitoring/. The application has no
# alerting of its own and never sends mail.
# metrics:
#   enabled: false

# ── Broker imports ──────────────────────────────────────────────────────────
imports:
  # Whether a broker CSV may create instruments it does not recognise. Off
  # means an unknown ticker stops the import so you can check it first.
  # allow_new_instruments: false

  # 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 the statement designer keeps the layouts you build, and its scratch
  # renderings. `templates_dir` holds work you want to keep, so it belongs on
  # a volume that survives a restart. `visual_dir` is scratch — page images
  # from a document you are mapping, deletable at any time, and it must NOT be
  # inside the data directory that gets backed up. So is `staging_dir`, where
  # a broker CSV's rows wait between the preview and the commit.
  # 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
```

## Optional features

Whole sections of the application, switchable. Turning one off removes its
navigation, its pages and its API routes — not just the link — so an install
that does not want a feature is not carrying a door to it.

```yaml
features:
  # The DCA calendar: a rotation of what to buy next, on an interval, for an
  # amount. Off means /schedule is gone and nothing prompts about it.
  # dca_schedule: true
```