Aller au contenu principal
Version: v5

Settings

Two halves, one page. The line between them is not infrastructure on one side, preferences on the other — that is an intention, and it decides nothing about a port number. The line is a test you can apply name by name:

What stays in the environment is what the process must know before it can open the store. Everything else lives in the store.

What you set in the app

The dials live in the store and nowhere else. There is no environment form for them, no precedence rule, no seed on first boot, no settings file: exactly one place says what a setting is worth, and that place is the one the app shows you. A key that has never been written is not a hole — it reads as the default written in the code, which is what lets a later version add a dial without migrating anything.

None of them requires a restart. A dial that would have needed one was deleted rather than moved, so the settings form has a single class of field.

DialDefaultWhat it decides
base_currencynonethe currency every figure is reported in
regular_interval120seconds between two polls of a symbol whose market is open
backfill_interval60seconds between two runs of the rebuild job
backfill_delay10seconds of politeness between two requests to Yahoo! Finance
backfill_chunk_days365days of history fetched per request
staleness_horizon900seconds before a frozen price is reported as stale; 0 disables the check

That is the whole list. You turn them on the Settings page of the app, or with one call on the write API, which is always served on the same port as the page.

The base currency

It is the one setting with no default, and the only thing the app ever asks you. It has no default on purpose: a default would silently interpret every amount you have already recorded, so not answered yet and answered have to stay two different states.

It is changeable while your ledger is empty and fixed from your first event. With no event there is no held symbol and therefore no stored price, so nothing has been interpreted and nothing can be reinterpreted; what is unrecoverable is reinterpreting amounts, never answering late or thinking again beforehand.

Until it is answered, nothing refuses: prices are still fetched in their native currency, nothing is converted, and no performance figure is written at all — not zeros, not blanks. The app says so on screen rather than showing you a number it cannot justify. An import that declares its own currency sets it, which is what lets an install answer the question without a single call.

Two of these are not decorative

Changing regular_interval reaches only the symbols whose market is open right now. Each symbol schedules its own next poll; a symbol whose market is closed is asleep until it reopens and reads the new value when it wakes. So the change is not portfolio-wide the instant you save it, and nothing is broken when it is not.

It also rescales, retroactively, the back-off of a symbol that is already failing. When a symbol stops returning a usable price, the wait between attempts grows as a multiple of this intervalregular_interval × 2^(n−3) after the third consecutive failure, capped at 24 hours — rather than as an absolute delay stored somewhere. Lowering the interval therefore shortens the wait of a symbol that has been silent since this morning, and raising it lengthens it. No interface can hide that, because the number in the form is the number in the formula. How prices are collected has the rest of the back-off.

What the container imposes

Three names. This is the complete list of what the app reads from its environment; nothing else in it is consulted.

VariableDefaultWhat it decides
SB_STORE_DIR/datathe directory the store lives in
SB_WEB_PORT8080the socket the app is bound to
LOG_LEVELINFOhow loudly the app logs
  • The path is a directory, never a file. The app names its own store file inside it. That removes the entire class of mistake where you point at a file whose parent directory is not mounted.
  • A variable that is set and no longer read is named at start-up, in one grouped line, and obeyed by nothing. SB_IMPORT_DIR is one of those: the folder it named is gone, and a file is handed to the app instead.
  • The defaults describe the container, and it is the deployment without Docker that overrides them — see Install without Docker. A plain docker run sets none of these.
  • A blank value counts as unset. An undefined substitution renders as an empty string, and an empty string is not an answer.
  • The port passes the test twice. It is read in the server's master process before the app is even imported, so it cannot come from the store; and a port changed from the interface would cut the connection the interface arrived by. That is a nature, not only a sequence.
  • There is one socket, and everything is behind it. The page, the reads and the writes — POST /api/events, PUT /api/settings, DELETE /api/store/orphans included — and /health all answer on SB_WEB_PORT. There is no second port to publish and no port that serves a filtered view of the app.
  • LOG_LEVEL is here because the most likely failure of this app is the store failing to open, and a log level kept inside the store cannot report that.
  • No secret lives in the environment any more. There was exactly one, the database token, and it has no subject left.

There is no switch that turns a half of the app off

There is no variable that disables the interface, and there will not be one. There used to be a second one — /metrics, on a socket of its own — and it is gone: one owner, one browser and one container paid for a second interface twice, once to build it and once in the shape it forced on the model. What is left is not a smaller version of it; it is the app's own pages, and they are where an installation now reports on itself.

So publishing the port publishes everything. Whoever reaches it reaches the page, the reads and the writes — POST /api/events and PUT /api/settings included. There is no port that is a filtered view of another, because there is no other port. If the app must not be reachable from a network, that is a decision for the network: bind the container to 127.0.0.1, or put it behind something that authenticates. The app itself has no half to serve.

What version 4 said, and is no longer read

Every SB_* or INFLUXDB_* variable that is present in the environment and is not one of those above is named at start-up and never obeyed. It produces one grouped notice, not one line per variable, saying which names were found and that these settings now live in the app. The list is computed — what is present, minus the boot variables, minus the dials — so it cannot drift out of date the way a hard-coded list of names would.

No longer readWhere it lives now
SB_REGULAR_INTERVALthe regular_interval dial
SB_BACKFILL_INTERVALthe backfill_interval dial
SB_BACKFILL_DELAYthe backfill_delay dial
SB_BACKFILL_CHUNK_DAYSthe backfill_chunk_days dial
SB_STALENESS_HORIZONthe staleness_horizon dial
SB_INGESTION_INTERVALnowhere — there is no polling left: a replay follows the write that changed the ledger
SB_SCRAPING_INTERVALnowhere — already deprecated in version 4, and its heir is a dial
SB_PERF_INTERVALnowhere — the performance series is a cache, rebuilt in full every two minutes; there is nothing left to trade off
SB_DYNAMIC_EXECUTOR_POOLnowhere — sizing is always automatic
SB_EXECUTOR_POOLnowhere — same reason; it was the one dial that needed a restart, so it was deleted rather than moved
SB_CONFIG_MODEnowhere — there is one mode, and files are no longer the truth
INFLUXDB_HOSTnowhere — the store is embedded
INFLUXDB_TOKENnowhere — the app has no secret to be given
INFLUXDB_DATABASEnowhere — same reason

SB_VERSION, SB_CONFIG_DIR, SB_UID and SB_GID are a different case again: they carry the prefix but were never read by the app. They belonged to the Compose file, and the Compose file is gone. They are not in the notice, because naming them there would suggest the app once obeyed them.

There is no .env.example

This inventory has two readers — this page and the app — and one source. A file of example variables would be a third writer of the same list, and the one nobody re-reads at release time.

Coming from a version 4 install? Coming from v4 covers what to do with the rest of it.