OFFSET偏移
← All articlesOFFSET / FIELD NOTESNO. 005

If configuration is reproducible, why do apps still forget?

From GNOME defaults and Home Manager activation to Flatpak's management scope, distinguish declarations, preferences, and data.

English translation · Code and commands follow the Chinese original.

IN THESE READING PATHSYou can rebuild the environment. What about its state? ↗NixOS system foundations and configuration ↗
About 7 min readStart reading

Once software, services, and desktop options are written into a Nix configuration, it is easy to expect one rebuild to restore the entire environment. But that environment also contains writable preferences, application databases, documents, and caches. Identifying who manages each kind of content tells you what the configuration can bring back and which backups you still need.

A declaratively described environment is not a complete disk snapshot#

NixOS configuration describes the desired state of software, system services, and other components. Building produces the corresponding result, and activation connects it to the running environment. System generations let you return to an earlier configuration, but they do not also restore documents, chat histories, or application databases to that point in time. System rollback and data recovery are separate mechanisms.[1]

01 / BUILDBuild the environment
02 / ACTIVATEApply the configuration
03 / RESTORERestore the data

Directory boundaries alone are not enough either: within ~/.config, some files may be links generated by Home Manager, while applications write others themselves. dconf can likewise contain both declaratively written values and values a user has changed in the interface. Location is only a clue; what matters is who actually manages the content and when it is written.

“Runtime state” does not mean worthless, nor does it mean impossible to version. Text preferences can be selectively exported, and business data can be backed up. You simply cannot expect a system to know what to restore if that content was neither declared nor backed up. Conversely, state containing secrets, history, or machine-specific paths generally should not go straight into a public configuration repository.

GNOME: defaults, user values, and locks#

GNOME’s GSettings provides applications with a typed settings interface. Schemas define keys and their defaults, while the commonly used dconf backend stores writable settings. The effective value a user sees may come from the user database or fall back to a system-provided value or schema default. Administrators can also organize databases and locking rules through dconf profiles.[3]

The NixOS GNOME module provides extraGSettingsOverrides for supplying schema defaults. The example below uses the current module’s option path to enable battery percentage, the day of the week, and Night Light by default. It is a fragment for an existing GNOME system configuration, not a complete system configuration.[5]

services.desktopManager.gnome.extraGSettingsOverrides = ''
  [org.gnome.desktop.interface]
  show-battery-percentage=true
  clock-show-weekday=true

  [org.gnome.settings-daemon.plugins.color]
  night-light-enabled=true
'';

Changing a default does not guarantee that it will override a value the user has already set explicitly. This explains a common situation: a new account behaves as expected, while an older account keeps its previous preferences. First check for a higher-priority user value instead of assuming every setting that fails to take effect means the build failed.

A default is not a lock either. If users must be prevented from changing a key, dconf locks need to be designed separately; ordinary schema overrides and settings written by Home Manager are not equivalent to that mechanism.[4]

Home Manager writes settings during activation#

To express a particular user’s stable preferences, use Home Manager’s dconf.settings. Paths here use dconf’s slash-separated groups. They look similar to the dot-separated schema names used by the gsettings command, but the syntax differs.

dconf.settings = {
  "org/gnome/desktop/interface" = {
    color-scheme = "prefer-dark";
    clock-show-weekday = true;
    show-battery-percentage = true;
  };
};

The implementation checked here generates and loads the corresponding settings during activation. It is not a daemon that continuously watches the database: after activation, the interface or an application can still change writable values unless another locking mechanism is in place. The next relevant activation applies the declaration again. The configuration specifies what gets written and when; it does not establish that the settings can never change.[2]

A successful build alone does not prove that these values have been written to the current session. When troubleshooting, check each step separately: whether this Home Manager configuration is being used, whether activation completed, whether key paths and types are correct, and what the application currently reads. Arrays, enums, and special numeric types must follow the relevant schema. Successful evaluation of a Nix expression is not enough to establish semantic correctness.

When removing a key from the declaration, also check the module’s cleanup behavior. This implementation handles keys that were previously managed and later removed. Deleting a declaration should not simply be understood as keeping the last written value forever.

Selectively bring preferences tested in the interface back into configuration#

There is no need to guess every setting key at once. Change one specific option in the interface, inspect the small, relevant set of data, and write it into configuration once you understand it. The commands below target only the desktop interface group, avoiding an export of the entire dconf database and its unrelated state.

gsettings get org.gnome.desktop.interface color-scheme
dconf read /org/gnome/desktop/interface/color-scheme
dconf dump /org/gnome/desktop/interface/ > desktop-interface.ini

gsettings get reads settings through the schema interface, whereas dconf read and dump operate on values in dconf; they do not answer exactly the same question. When no value has been explicitly written, a schema default may still provide an effective setting to the application. A dump is an extract of settings in that subtree, not automatically a list of every setting that differs from its default.

Next, select the keys you want to retain long term, remove temporary state and machine-specific values, and convert the values to the types the module requires. A small, explicit declaration is easier to maintain than copying the entire database into a large configuration you do not understand.

The verification order should be clear too: successful evaluation and building only validate the preceding structure. After authorized activation, query the settings and observe the application’s actual behavior. An export is a record of observations, and its contents still need review before being added to Git.

Software installation lists also have a limited scope#

Removing a package from environment.systemPackages changes the set of software supplied by the new system configuration. It does not mean that the program immediately disappears from every source on the machine: another user profile, a container, or Flatpak may still provide a program with the same name, and corresponding objects may remain in old generations and the store. To determine which one is actually running, check the resolved path.

Declarative Flatpak management is particularly useful for examining scope. Taking the external nix-flatpak module as an example, and assuming its NixOS module has already been imported, you can declare applications in the configuration:

services.flatpak = {
  enable = true;
  packages = [ "org.gnome.Calculator" ];
  uninstallUnmanaged = false;
};

This relies on options such as packages supplied by the nix-flatpak module. Simply enabling NixOS’s basic Flatpak service does not automatically provide these capabilities; the relevant remote must also be available. For the Home Manager version, import the project’s Home Manager module and interpret its behavior within the per-user installation scope.

By default, this module manages declared items and leaves other manually installed applications within its scope in place. Only setting uninstallUnmanaged = true requests removal of undeclared items, and that remains bounded by installation scope: system installations and user installations are different sets of objects. This option changes what the module is allowed to remove; it should not be enabled casually as though it were a formatting preference.[6]

The application is back; its content still needs to be restored#

Installing an application and preserving its data are separate concerns. An ordinary Flatpak uninstall can retain application data, while uninstalling with --delete-data removes the corresponding data it manages. Files the application wrote elsewhere may still exist separately. Removing software from a list is therefore not the same as deleting all of its personal data.[7]

For notes, projects, databases, and application state that needs to persist, establish where it is stored, whether backups cover it, whether the application must stop writing during restoration, and whether the target version can read it. Backing up startup configuration alone does not automatically include the database; copying a data directory alone does not guarantee that migration between versions needs no additional work.

Decide whether to retain a cache based on the cost of rebuilding it. Being regenerable does not mean it is always worth rebuilding, nor does it mean that no configuration interface exists. Answering “Should this be retained?”, “How is it generated?”, and “Who generates it?” separately is more practical than drawing a blanket boundary by file extension.

Give each kind of state a clear home#

ObjectSuitable management approachWhat to verify
Long-term software and servicesNix declarations and locked dependenciesBuilding, activation, and actual operation
Stable desktop preferencesSelect keys and declare them in the appropriate moduleWrite timing, priorities, and override behavior
Experimental adjustmentsKeep observation records; add to configuration once stableWhether they are worth adopting long term
Documents, databases, and important stateBackups, synchronization, and restoration proceduresIntegrity, version compatibility, and readability
Regenerable cachesRetain or rebuild according to costWhether restoration time is acceptable

Acceptance testing for an environment can be concrete: choose an important application and verify, in a separate location, that the software reinstalls, the settings take effect, and the data can be read. Giving the configuration repository and backups clear, separate responsibilities makes moving to a new machine and rolling back predictable.

References & further reading

  1. NixOS: system rollback ↗
  2. Home Manager dconf activation implementation ↗
  3. GNOME: dconf profiles and precedence ↗
  4. GNOME: locking settings ↗
  5. NixOS GNOME module ↗
  6. nix-flatpak declarations and cleanup scope ↗
  7. Flathub: uninstallation and application data ↗

Adapted from technical notes. Examples use generic names.

Continue along a reading path

You can rebuild the environment. What about its state?

  1. 01If configuration is reproducible, why do apps still forget?
  2. 02What does Distrobox actually export?

NixOS system foundations and configuration

  1. 01NixOS: the foundations of a declarative system
  2. 02Installing NixOS: installation media, partitions, and initial configuration
  3. 03NixOS configuration basics: options and modules
  4. 04Managing a system with Flakes and Home Manager
  5. 05The NixOS command line: old and new interfaces and everyday maintenance
  6. 06NixOS generation management and garbage collection
  7. 07Specialisation: multiple configuration variants in one generation
  8. 08If configuration is reproducible, why do apps still forget?
CONTINUE READING / NEXT ARTICLEWhat does Distrobox actually export?

Walk through GUI and CLI exports, arguments, wrapper scripts, shared paths, troubleshooting, and removing exported entries.