First-Run Core Entry Point

1. Overview

The First-Run Core Entry Point module (first-run-main.cpp) implements the startup routine for BastionGuard’s first-run experience. Its primary responsibility is to ensure that localization, environment variables, and UI styling are configured deterministically before the GTK application window is created.

The module:

  • Loads user language/environment preferences from ~/.config/BastionGuard/lang.conf
  • Applies locale settings via setlocale(LC_ALL, "")
  • Initializes gettext domain bindings for BastionGuard translations
  • Initializes the GTK application instance
  • Loads the shared CSS/UI theme via load_style()
  • Creates and runs the first-run window (FirstRunServicesWindow)

2. Integrated Components and Dependencies

The entry point integrates the following libraries and internal modules:

  • GTKmm – application lifecycle and window creation (Gtk::Application)
  • glibmm/i18n – gettext wrapper integration and _() macro usage
  • POSIX localestd::setlocale() and environment propagation
  • FirstRunServicesWindow – first-run UI window implementation (first-run-services-window.hpp)
  • StyleProvider – shared styling loader (load_style())
  • Resource – resource path helpers used indirectly by style/UI components

The build requires LOCALEDIR to be defined for this target; compilation fails fast if it is missing.


3. Language Configuration Loading

3.1 Configuration File Location

User language/environment configuration is loaded from:

~/.config/BastionGuard/lang.conf

This file is expected to contain key/value assignments similar to:

  • LANG=it_IT.UTF-8
  • LC_ALL=it_IT.UTF-8
  • LANGUAGE=it_IT:en_US

3.2 Parsing Rules

The loader (load_lang_conf()) applies a conservative parsing model:

  • Ignores empty lines
  • Ignores comment lines beginning with #
  • Requires an = delimiter
  • Splits into key and val
  • Performs minimal trimming of surrounding whitespace for both key and value
  • Ignores entries where key or value is empty after trimming

3.3 Environment Application

Each valid entry is applied to the current process environment using:

setenv(key.c_str(), val.c_str(), 1);

The overwrite flag is set to 1, ensuring that user configuration takes precedence over inherited environment values.

The function returns:

  • true if at least one variable was applied
  • false if the file does not exist, cannot be opened, or contains no valid assignments

4. Locale and gettext Initialization

4.1 Initialization Order

The startup sequence enforces a strict order:

  1. Load lang.conf and apply environment variables
  2. Apply locale via std::setlocale(LC_ALL, "")
  3. Initialize gettext domain bindings

This ordering is critical: gettext and locale resolution depend on the effective environment variables (e.g., LANG, LC_ALL, LANGUAGE).


4.2 gettext Domain Configuration

Translations are configured using the BastionGuard text domain and build-provided locale directory:

  • bindtextdomain("BastionGuard", LOCALEDIR);
  • bind_textdomain_codeset("BastionGuard", "UTF-8");
  • textdomain("BastionGuard");

This ensures:

  • Translation lookup is scoped to the BastionGuard domain
  • Returned strings are UTF-8 encoded

4.3 Diagnostics

After initialization, the module logs a localized diagnostic indicating whether lang.conf was used:

  • If loaded: indicates configuration was read from the user config path
  • If not loaded: indicates system locale will be used

Messages are emitted only after gettext initialization to ensure they are safe to wrap with _().


5. GTK Application Lifecycle

5.1 Application Creation

The GTK application instance is created with a fixed application ID:

Gtk::Application::create("org.bastionguard.first-run-test")

This ID is used by GTK for application identity, session integration, and single-instance semantics (where applicable).


5.2 Styling Initialization

The module applies shared styling before presenting UI components:

load_style();

This step typically loads the application CSS provider and ensures consistent visual identity with the rest of BastionGuard.


5.3 Window Creation and Run Loop

The first-run window is created and executed using:

return app->make_window_and_run<FirstRunServicesWindow>(argc, argv);

This API:

  • Instantiates the FirstRunServicesWindow type
  • Starts the GTK main loop
  • Returns the process exit code when the window closes

6. Build-Time Requirements

6.1 LOCALEDIR Definition

The module requires LOCALEDIR to be defined at compile time. If missing, compilation fails with:

#error "LOCALEDIR not defined for this target"

This ensures gettext is always initialized with a valid translation directory path for the current build target.


7. Runtime and Security Considerations

  • Deterministic localization: language configuration is applied before setlocale and gettext setup to avoid mismatched translations
  • Scoped environment changes: setenv() affects only the current process (and children) and does not modify system-wide locale settings
  • Minimal parsing surface: the config parser accepts only simple key/value assignments and ignores malformed lines
  • UTF-8 enforcement: gettext codeset is fixed to UTF-8 to prevent encoding inconsistencies in the UI
  • Operational transparency: localized diagnostics indicate whether user language overrides were applied
  • Separation of concerns: the entry point only orchestrates initialization; first-run logic is delegated to FirstRunServicesWindow