diff --git a/CLAUDE.md b/CLAUDE.md index 9f986a5..0242b44 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,7 +47,7 @@ This project uses `just` (see `justfile`) and Nix directly. `proxyServer = "caddy"` — so both proxy backends get exercised. The shared assertion suite (factored into a `check_invoiceninja(machine, proxy_unit)` function in the test script) runs against each node: waits for mysql/redis/phpfpm/queue-worker/proxy to be active, checks the - `invoiceninja-data-setup` marker files (`.initial-migration`, `.db-seeded`, `.admin-created`) to confirm the + `invoiceninja-data-setup` marker files (`.db-seeded`, `.admin-created`) to confirm the one-time setup actually completed (not just that the oneshot unit exited), curls `/` for the app shell, exercises the `invoiceninja-manage` wrapper, logs in via `/api/v1/login` using the test admin credentials as an end-to-end check that migrations/seeding/account creation all worked, checks the full language list is @@ -99,8 +99,11 @@ expect Nix to report the correct hash on a failed build (or use `nix-prefetch`/` contents directly on disk in `dataDir`. - `invoiceninja-data-setup.service` (oneshot, runs on every activation) does all first-run and idempotent setup: clears bootstrap/opcode cache, rebuilds `.env`, rsyncs static storage into `dataDir`, symlinks the runtime - `app.php`, and guards one-time actions (`migrate`, `db:seed`, admin account creation) behind marker files - (`.initial-migration`, `.db-seeded`, `.admin-created`) in `dataDir`. `phpfpm-invoiceninja.service` and + `app.php`, runs `migrate --force` unconditionally on every activation (safe/idempotent — Laravel tracks + applied migrations in the DB and only runs new ones; this must NOT be marker-gated, since a later invoiceninja + version upgrade can ship new migrations that need to run on an already-migrated install), and guards the + remaining one-time actions (`db:seed`, admin account creation) behind marker files (`.db-seeded`, + `.admin-created`) in `dataDir`. `phpfpm-invoiceninja.service` and `invoiceninja-queue-worker.service` both depend on it. It also unconditionally runs `invoiceninja-manage cache:forget languages` on every activation, working around an upstream bug: migration `2025_08_31_224356_additional_languages_af_ca_id.php` runs during `migrate` (before `db:seed`'s diff --git a/nixos-module/invoiceninja.nix b/nixos-module/invoiceninja.nix index a7e272d..180fc3d 100644 --- a/nixos-module/invoiceninja.nix +++ b/nixos-module/invoiceninja.nix @@ -702,8 +702,11 @@ in # We cannot link the cache folder only because bootstrap folder needs to be writeable. ln -sf ${invoiceninja}/bootstrap-static/app.php ${cfg.runtimeDir}/app.php - # Perform the first migration - [[ ! -f ${cfg.dataDir}/.initial-migration ]] && invoiceninja-manage migrate --force && touch ${cfg.dataDir}/.initial-migration + # Run migrations on every activation, not just the first: `migrate` is idempotent + # (Laravel tracks applied migrations in the DB and only runs new ones), and gating this + # behind a marker file meant migrations shipped by a later invoiceninja version upgrade + # would never run. + invoiceninja-manage migrate --force # Seed database with records # Necessary for languages, currencies, countries, etc. diff --git a/tests/nixos-test.nix b/tests/nixos-test.nix index 4e354be..9565361 100644 --- a/tests/nixos-test.nix +++ b/tests/nixos-test.nix @@ -39,9 +39,10 @@ pkgs.testers.nixosTest { # phpfpm-invoiceninja/invoiceninja-queue-worker both Require= invoiceninja-data-setup, # so systemd already waited for it to finish above. Confirm the one-time setup steps - # it guards actually completed, rather than just that the unit exited. - with subtest("one-time setup (migrate, seed, admin account) completed"): - machine.succeed("test -f /var/lib/invoiceninja/.initial-migration") + # it guards actually completed, rather than just that the unit exited. (Migrations run + # unconditionally on every activation rather than being marker-gated, so there's no + # marker file to check for them here.) + with subtest("one-time setup (seed, admin account) completed"): machine.succeed("test -f /var/lib/invoiceninja/.db-seeded") machine.succeed("test -f /var/lib/invoiceninja/.admin-created")