Bootloader

The wippy/bootloader module orchestrates application initialization by discovering and running bootloader functions in a defined order at startup. Other framework modules (migrations, encryption, index refresh) register bootloaders to run their own initialization steps.

Setup

Add the module to your project:

wippy add wippy/bootloader
wippy install

Declare the dependency and the required application host:

version: "1.0"
namespace: app

entries:
  - name: processes
    kind: process.host
    lifecycle:
      auto_start: true

  - name: os_env
    kind: env.storage.os

  - name: dep.bootloader
    kind: ns.dependency
    component: wippy/bootloader
    version: "*"
    parameters:
      - name: application_host
        value: app:processes
      - name: env_storage
        value: app:os_env

The bootloader itself runs as wippy.bootloader:bootloader.service (a process.service with auto_start: true). Nothing else is required to activate it.

How It Works

At startup the bootloader:

  1. Discovers every entry with meta.type: bootloader from the registry.
  2. Sorts them by meta.order ascending (lowest first).
  3. Executes each one sequentially as a Lua function.
  4. Stops on the first error that returns status = "error".
  5. Reports total / success / failed / skipped counts when finished.

Bootloaders are autonomous — each one checks its own conditions, does its work, and reports a structured result.

Defining a Bootloader

A bootloader is any function.lua entry with meta.type: bootloader:

- name: seed_defaults
  kind: function.lua
  meta:
    type: bootloader
    order: 50
    description: Seed default rows for a new install
  source: file://seed_defaults.lua
  method: run
  modules:
    - logger
  imports:
    sql: :sql
Field Required Description
meta.type Yes Must be bootloader
meta.order No Execution order (default 100); lower runs first
meta.description No Human-readable summary
meta.requires No Dependency hints surfaced in logs

Return Contract

The method returns a table describing the outcome:

local function run()
    local ok, err = apply_seed()
    if err then
        return {
            status = "error",
            message = "seed failed: " .. tostring(err)
        }
    end

    if not ok then
        return {
            status = "skipped",
            message = "already seeded"
        }
    end

    return {
        status = "success",
        message = "seeded default rows"
    }
end

return { run = run }
Status Meaning
success Work completed
skipped No-op (already done, precondition unmet)
error Failure — stops the boot sequence

A bootloader that raises a Lua error is treated as error.

Execution Order

Lower order values run first. Reserve low orders for infrastructure:

Order Typical Use
10 Secrets and encryption keys (provided by the module)
20 Schema migrations (provided by wippy/migration)
50 Data seeding, search index warmup
100 Default — application-level tasks

When two bootloaders share an order, execution order between them is not guaranteed.

Built-in Bootloaders

Encryption Key (order 10)

Generates a 256-bit ENCRYPTION_KEY and stores it through the configured env_storage if no value is present. Other modules (security, usage tracking) read this variable for envelope encryption. Skipped when the variable already exists.

Migration Bootloader (order 20)

Provided by wippy/migration. Discovers every entry with meta.type: migration, groups them by meta.target_db, and applies the pending ones. See Migrations.

Observing Boot Status

The service logs one line per bootloader (SUCCESS, FAILED, SKIPPED) with the entry ID, order, and duration. The final summary line reports aggregate counts. A failed bootloader aborts startup — the supervisor's restart policy then applies to bootloader.service.

Keep bootloaders idempotent. They may run again after a crash restart, so check preconditions (row exists, file present, env var set) before doing work.

See Also