Docs/Start Here/Configuration File Versions and Upgrades
PlatformConcept

Configuration File Versions and Upgrades

Timebase configuration files carry a version, and a component migrates its own files forward when it starts. What changes, what is preserved, and what an upgraded install keeps that a fresh one does not.

Updated 1 Oct 2026Timebase Docs

Overview#

Timebase configuration files carry a version, and it is the Timebase release that last wrote the file:

{
  "version": "2.0.0",
  ...
}

A component reads that version when it starts. If the file predates a change of shape the component knows about, it migrates the file forward and writes it back. If the shape is already right but the release has moved on, it simply restamps the header. You do not run anything, and there is no separate upgrade step for configuration.

The header is a record, not a compatibility rule. It moves on every release, including patches and hotfixes, whether or not anything about the file's shape changed. So the version in your file answers "which Timebase last wrote this", and nothing more. What decides whether a migration runs is separate, and covered below.

This article covers what that means in practice: what is changed, what is left alone, and the one difference between an upgraded installation and a fresh one that is worth knowing about before you go looking for a setting that is not where you expect.

Which files carry a version#

Nearly every configuration document Timebase ships carries a version header. settings.config is the one every component has. The others are the collection files each component owns: clients.config and users.config in Pulse, historians.config in a Collector, connector.config in Connect, and so on.

Each file is versioned independently, and each component migrates its own. Nothing coordinates across components, so upgrading one does not require upgrading another at the same time.

The collection files carry a second version too#

A file holding a collection of records - collectors.config, sources.config, connector.config, services.config - opens with a schemas map beside its version:

{
  "version": "2.0.0",
  "schemas": { "Simulator": "2.0.0", "MQTT": "2.0.0", "OPCUA": "2.0.0" },
  ...
}

The two say different things. version describes the file's own shape. schemas describes the shape of the records inside it, one entry per kind of plugin present, so a file holding MQTT and OPC UA records carries a version for each.

The host mints an entry for every plugin type it finds in the file, and adds one when a record of a new kind is first written. As with version, the shipped templates carry no schemas map at all: a literal there would go stale, so the host writes it when it composes the file.

Both axes move with the release. Older releases seeded the schemas entries at 1.0.0 on an upgrade while a fresh installation got the release number, so the same binary described the same plugin's records two different ways depending on install history. They agree now. If you compare a long-lived installation against a fresh one, that is why an older file may show 1.0.0 against a plugin that a new file shows at the current release.

Two numbers, doing two different jobs#

It is worth separating these, because they look like one thing and behave differently.

What it is When it moves
The header in your file The release that last wrote it Every release
The migration steps a component carries The shapes it knows how to convert between Only when a shape actually changes

A release that reshapes nothing declares no migration step. Your files are still restamped with the new release, and nothing else about them changes.

A restamp does not rewrite the file. Only a genuine reshape reserializes it. A restamp patches the header in place, specifically so the comments survive - the shipped configuration files carry between fourteen and a hundred and thirty comment lines each, and a full rewrite would delete every one of them on every install at every release.

The shipped templates carry no version at all. A component mints the header when it first composes a template into a real file, at whatever release is doing the composing. That is why a fresh installation's file is stamped with the release you installed rather than with a number somebody typed into a template months earlier.

What happens at startup#

The file is What the component does
Already at the shipped version Nothing. The file is not rewritten, so its comments and formatting are untouched
Older than the shipped version Migrated forward, backed up, and written back
Missing Composed from the shipped template
Unreadable as JSON Startup stops, with the parse error named

Steady state writes nothing at all. A component that finds its file already current hands the text back byte for byte rather than re-serialising it. This matters more than it sounds: a needless rewrite would strip the comments in your file and trip every file-watcher pointed at it.

The backup#

When a migration does move the version, the component writes a backup beside the original before saving the new one. The backup is named for the version it came from:

settings.config          the migrated file
settings.config.v0.bak   your file as it was, before the migration

A file that has never carried a header reads as version 0, which is why an upgrade from an older Timebase produces .v0.bak. A later migration from a versioned file produces a backup named for that version instead, so two migrations never collide on one filename.

Each backup is written once. A migration that finds its backup already there leaves it alone.

A read-only file is not a failure#

If the component cannot write the migrated file - a read-only mount, a permissions problem - it logs a warning and runs from the migrated content in memory. The component still starts and still behaves correctly. Only the on-disk file stays old, so the migration runs again on the next start.

A read-only configuration mount is a supported deployment, not something to work around.

Whether the file on disk is still used depends on what the migration did. A migration that only restamped the header leaves the file perfectly usable: it holds the same shape, and only its version values differ, so the component keeps reading it and you keep both live reload and the ability to hand-edit it.

A migration that genuinely reshaped the document is different. There the file on disk describes the old shape, and reading it alongside the new one in memory would merge both - a section that a migration removed reappearing beside its replacement, or a pre-migration array surfacing beside the dictionary that replaced it. So a reshape that cannot be written means the on-disk file is set aside for that run, and you lose live reload until the write succeeds.

Since the header moves on every release, the restamp case is the common one, and it costs you nothing.

What the current migration does#

The migration that matters for an upgrade to 2.0 folds a component's OAuth client into the Pulse section.

Before, a component kept its Pulse address and its Pulse credentials in two places:

{
  "Auth": {
    "Enabled": true,
    "IdP": { "Host": "localhost", "Port": 4542, "UseTls": true },
    "ClientId": "Historian",
    "ClientSecret": "..."
  },
  "Pulse": { "Host": "localhost", "Port": 4542, "UseTls": true }
}

After, there is one:

{
  "version": "2.0.0",
  "Pulse": {
    "Host": "localhost",
    "Port": 4542,
    "UseTls": true,
    "Auth": {
      "Enabled": true,
      "ClientId": "Historian",
      "ClientSecret": "..."
    }
  }
}

Auth:IdP is gone. The address of the identity provider is Pulse:Host, Pulse:Port and Pulse:UseTls, so there is no longer a second copy that can drift out of step with the first.

Pulse itself is not folded. Its root Auth section configures Pulse as the identity provider - the Issuer, and whether authentication is on at all - rather than describing a client of one.

Historian's migration also adds the PGWire settings, which did not exist in the older file.

What an upgraded install keeps that a fresh one does not#

A migration never overwrites a value you already have. It reshapes where a setting lives; it does not change what the setting says. That includes values that are empty, false, 0 or null - all of them are values you chose, and all of them are preserved.

The practical consequence is worth stating plainly:

An upgraded installation keeps Pulse:Auth:Enabled set to whatever it was, even where a fresh installation of the same version would ship it as true.

So if a component had authentication switched off before the upgrade, it still has it switched off afterwards, and its AI assistant is still not offered. That is not a fault in the migration. It is the migration refusing to make a security decision on your behalf.

The same applies to settings that are new in this release. The migration adds only what a component genuinely cannot run without. Sections such as Pulse:Gage are not added to an existing file, so an upgraded component uses the built-in defaults for them until you add the section yourself. Copy the block you want out of a fresh installation's settings.config, or out of Component Settings.

After an upgrade, check these two#

  1. Pulse:Auth:Enabled. If you want the AI assistant, this must be true, and it will still be whatever it was before. See Quick Start: Turn On the AI Assistant.
  2. Pulse:Auth:ClientSecret. The fold moves your existing secret across unchanged, so this only needs attention if it was never set.

Troubleshooting#

What you see What it means What to do
A warning naming the configuration file, and the component starts anyway The migrated file could not be written, so the component is running from memory Check the file's permissions and whether the mount is read-only. Nothing is broken; the migration will run again next start
The component refuses to start, naming a parse error The file is not valid JSON. A hand-edit is the usual cause Fix the syntax. Comments and trailing commas are allowed; an unclosed brace is not
A setting you expected is missing from the file after an upgrade Migrations do not add optional sections to an existing file Copy the section from a fresh installation, or from Component Settings
A setting reverted to an old value It did not. The migration preserved what your file already said Compare against the .bak file beside it to see what your file carried before
esc
↑↓ navigate · ↵ open · ⌘K anywhere